Intro to PostGraphile V5 (Part 1): Replacing the Foundations
Tags: postgraphile, graphql, postgres, grafast
Published: 2023-06-20 15:21:39

> _Benjie is the community&ndash;funded maintainer of the Graphile suite &mdash; a collection of MIT&ndash;licensed Node.js developer tooling for GraphQL and/or PostgreSQL. A key project in this suite is PostGraphile, a high performance, customizable and extensible GraphQL schema generator for APIs backed primarily (but not exclusively!) by PostgreSQL._
> 
> _In this post Benjie tells his story of bringing a new paradigm to PostGraphile to enable it to become more powerful and performant than ever before &mdash; this new system is coming in PostGraphile Version 5, available in pre&ndash;release now for all Graphile sponsors, find out more at [postgraphile.org](https://postgraphile.org)_

Something was fundamentally problematic in the design of PostGraphile Version 4 &mdash; the look&ndash;ahead system. The system that gave PostGraphile its power and performance was also the system that made it hard to maintain and hard to add new functionality to. Though few ever needed to interact with it directly, it was hard to teach those who did how to use the look&ndash;ahead system when even I struggled with it &mdash; and I was the one who wrote it! I built abstractions to make the common tasks more straightforward for users, but everything needed its own little helper, and the helpers were inconsistent. Messy. Verbose.

I wanted PostGraphile V5 to support all the new features of GraphQL, but even patching well&ndash;established features such as polymorphism into the look&ndash;ahead system seemed insurmountable &mdash; it wasn’t built with that in mind. This was hardly a surprise, the original system was hacked together in just 2 weeks! I needed a better solution…

**It was February 2020**. I had returned from the [FOSDEM](https://fosdem.org/) conference in Brussels and was lying sick in bed with a fever. (Immunosuppressants and conferences are not a winning combination.) As I went in and out of fevers, a thought struck me… **What if instead of writing code that executes, we wrote code that _described_ the execution.** GraphQL is declarative, but resolvers are procedural… What if, instead, we made declarative resolvers?

When I could lift my head without the room spinning, I scribbled some notes in my notebook. I started by doing what I hadn't with look&ndash;ahead: focusing on the code the users would write. In fact, that's all I focused on, and just kept telling myself that all the information the system needed was there in that code… I just needed to build a system that could understand and execute it. 

<figure>
<a href="https://www.graphile.org/ext/notebook.png"><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/32fqcs333dqxf396poyw.png" alt="A photo of an A4 notebook with lined paper and scrawly handwriting in multiple pen colours talking about 'plans' and various other technical details that are hard to extract" /></a>
<figcaption><p>
The page that got it all started! Scribbled out in Feb 2020 (over multiple sittings). Though the planning system has evolved since then, you can still see a lot of the things that Gra<em>fast</em> has today!
</p></figcaption>
</figure>

That turned out to be a long process with many false starts. Over the 3 years I spent working on this project (on and off, between client work and open source responsibilities, grabbing a few hours here and there to peck away at it) I rewrote the engine 5 times. Each time I ended up with something that worked, mostly, but it would fail at some point near the end due to some slight oversight, each time making me revisit the drawing board.

Throughout these rewrites, the "plan resolver" code that users would write (or PostGraphile would generate for you) barely changed &mdash; it was just the engine that needed to evolve. "Plan resolvers" differ from traditional GraphQL resolvers in that instead of calling the code to do the work of fetching the data necessary to fulfil the request (as traditional resolvers do), they serve to build a "field plan" that describes the required actions (or "steps"). The system then combines all of these field plans into an "operation plan", and it can manipulate the steps in this plan &mdash; merging, replacing, reorganizing and optimizing &mdash; ultimately resulting in a highly optimized plan that minimizes the work that backend services need to do.

<figure>
<a href="https://www.graphile.org/ext/plan-code.png"><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ld592d4u6rg7xrtyy9k9.png" alt="A screenshot showing GraphQL type definitions on the left and 'plan resolvers' on the right; the plan resolvers are between 1 and 3 lines of code each and look similar to traditional resolvers" /></a>
<figcaption><p>
Example plan resolvers, similar to the ones that PostGraphile V5 builds internally. Despite their apparent simplicity, they express everything the system needs to know in order to build highly efficient queries against your backend. Note that plan resolvers are entirely synchronous: no data is fetched during planning &mdash; the fetching comes later, once the plan has been fully processed and optimized.
</p></figcaption>
</figure>

I saw this stability of the plan resolvers despite major changes to the planning and execution engine as a validation of the core of my idea &mdash; the abstraction was right, I just needed to figure out how to build the final engine.

**And, build it, I did!** (Eventually…)

One of the final breakthroughs I had was that piggy&ndash;backing off of GraphQL.js' execution model just wasn't going to let me do everything I needed the system to do, at least not as quickly as I wanted it to do it!

<figure>
<a href="https://www.graphile.org/ext/flamegraph.png"><img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/navv4d8mdnnmm4xzy2p9.png" alt="Flame graph showing about 25% 'Crystal execute', 60% GraphQL.js (synchronous) and 15% Fastify" /></a>
<figcaption><p>
A profiling <a href="https://www.brendangregg.com/flamegraphs.html">flame graph</a> from Graphile Crystal (a precursor to Grafast) using GraphQL.js' executor (each tick is 1ms, total: 29ms). As we removed more and more responsibilities from GraphQL.js, we ended up only using it for output. Replacing this final responsibility with a custom implementation in Graphile Crystal itself, we reduced execution time for this query down to 15.5ms (effectively removing the majority of the yellow portion of the flame graph).
</p></figcaption>
</figure>

So, rather than continuing to squeeze a declarative holistic execution system inside of a linear execution system, I decided to write my own GraphQL execution engine. Ultimately, this resulted in Gra<em>fast</em>, the next generation planning and execution engine for GraphQL! You can find more about this and what makes it so exciting, not just for PostGraphile but for the entire GraphQL ecosystem, in this video:

{% youtube H26uBe_lLag %}

Gra<em>fast</em> solved all of my issues with look&ndash;ahead, and was more powerful and flexible too! It was, and remains to be, a joy to work with. Gra<em>fast</em> is better than look&ndash;ahead in every way I cared about: plans are simpler to write and maintain, easier to read, they execute faster, they are more capable &mdash; handling polymorphism and incremental delivery and all these other fun things that GraphQL gives us access to &mdash; and the SQL queries that the system built for V5 were so much simpler and easier to read (and faster to execute!) than those in V4.

<figure>
<a href="https://www.graphile.org/ext/v4-query-generation.png">
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jms4g8rae269tyf1cwjo.png" alt="GraphQL query (left) and SQL for that query generated by v4 (right). The SQL on the right is complex and has many levels of nesting." /></a>
<figcaption><p>
A GraphQL query (left), accompanied by the SQL generated by PostGraphile V4 from that query (right). The generated query is tightly coupled with the structure of the GraphQL request and this leads to verbose and overcomplicated SQL.
</p></figcaption>
</figure>

<figure>
<a href="https://www.graphile.org/ext/v5-query-generation.png">
<img src="https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p5zij4mlorcvwfwi37ym.png" alt="GraphQL query (left) and SQL for that query generated by v5 (right). The SQL on the right is around one quarter of that generated by V4 and is much easier to read." /></a>
<figcaption><p>
The same GraphQL query as above (left), accompanied by the SQL generated by PostGraphile V5 from that query (right). The SQL is much simpler and faster to execute than that produced by V4; not to mention also much easier to read and understand.
</p></figcaption>
</figure>

But!

There was a problem.

A big problem.

PostGraphile V4 was entirely built on top of the look&ndash;ahead engine. Every type, every field, every argument interacts with the look&ndash;ahead engine. And I'd relegated the look&ndash;ahead engine to the trash! With the look&ndash;ahead system removed, almost nothing worked.

So I set about doing what we're always told not to do: I rebuilt everything. From scratch.

**And, since I was rebuilding it all anyway, I could solve a few of the other problems that had been bugging me…** 🤔

> _~~Check back next week for~~ Next, check out [Part 2: Plugins and Presets](https://dev.to/graphile/intro-to-postgraphile-v5-part-2-plugins-and-presets-26fe); and remember: to get your hands on a pre&ndash;release version of PostGraphile V5, all you need to do is sponsor Graphile at any tier and then drop us an email or DM! Find out more at [graphile.org/sponsor](https://graphile.org/sponsor)_