|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Major Grafast beta: three down, one to go" |
| 4 | +date: 2025-03-24T17:00:00Z |
| 5 | +path: /news/20250324-major-grafast-beta/ |
| 6 | +thumbnail: /images/news/grafast-wordmark-2023.svg |
| 7 | +thumbnailAlt: "A developer looks at her monitor while holding a cup of tea." |
| 8 | +tags: announcements, releases, grafast, postgraphile |
| 9 | +noToc: false |
| 10 | + |
| 11 | +summary: |
| 12 | + "This release contains more than 3 months of work, and is a major step towards |
| 13 | + release readiness." |
| 14 | +--- |
| 15 | + |
| 16 | +_Announced 2025-03-24 by Benjie_ |
| 17 | + |
| 18 | +<p class='intro'> |
| 19 | +In the first Gra<em>fast</em> Working Group, we outlined 4 <em>major</em> issues in Gra<em>fast</em> |
| 20 | + that needed to be addressed before we could think about general release. With |
| 21 | + this release, 3 of these are now complete! |
| 22 | +</p> |
| 23 | + |
| 24 | +- ✅⤵️ Global dependencies - solved via "unary" steps |
| 25 | +- ✅⤵️ Early exit - solved via "flags" |
| 26 | +- ✅🎉 **Eradicating eval - this release!** |
| 27 | +- 🤔🔜 Polymorphism |
| 28 | + |
| 29 | +After 3 months of gruelling work, we're proud to announce that the third of |
| 30 | +these, eradicating eval, is now addressed with the launch of |
| 31 | +`grafast@0.1.1-beta.21` (used as the core execution engine in |
| 32 | +`postgraphile@5.0.0-beta.39`). Let's look into what that has meant. |
| 33 | + |
| 34 | +## Input evaluation moved to runtime |
| 35 | + |
| 36 | +Since the beginning, Gra*fast* has had the ability to add plan resolvers not |
| 37 | +just to fields, not just to arguments, but also to input object fields |
| 38 | +(including those within lists). This made Gra*fast*'s planning really ergonomic |
| 39 | +for things like nested filters, which was great for PostGraphile! But it turns |
| 40 | +out it's really problematic for certain shapes of input — planning would put |
| 41 | +constraints on the variables compatible with the plan, requiring potentially |
| 42 | +unlimited numbers of operation plans needing to be built for the same GraphQL |
| 43 | +document. Worse: for large input trees involving lists, the number of steps |
| 44 | +generated could be overwhelming, resulting in the deduplication phase taking |
| 45 | +excessive time. |
| 46 | + |
| 47 | +One particular user example that could cause 4 minutes of planning time from |
| 48 | +just a 100kB input made it clear that we had overreached with using plan |
| 49 | +resolvers too deep into inputs; so we've scaled it back so that you can only add |
| 50 | +plan resolvers to fields and arguments, you can no longer attach `applyPlan` or |
| 51 | +`inputPlan` to input object fields. This was something that we used a lot |
| 52 | +internally (hence the huge time investment migrating away!), but very few people |
| 53 | +(no-one?) used externally so it was ripe for removal. |
| 54 | + |
| 55 | +That problematic query that took 4 minutes to plan before? It now takes 1.1ms to |
| 56 | +plan, yielding a 200,000x speedup! |
| 57 | + |
| 58 | +### What does this mean for my codebase? |
| 59 | + |
| 60 | +Hopefully good things! Please update to the latest `@beta` of all the |
| 61 | +PostGraphile and/or Gra*fast* related modules you're using (including plugins) |
| 62 | +and for most users everything should work as before, only better. |
| 63 | + |
| 64 | +I've outlined some of the most common changes you may need to make below, but if |
| 65 | +you are impacted by any other changes, please ask for help in the chat — AFAIK |
| 66 | +most of the other things that have had significant changes are used by almost |
| 67 | +no-one except me, so it doesn't make sense for me to invest time documenting it |
| 68 | +here. If you're curious, many items are documented in both the changelogs and |
| 69 | +the pull requests where the changes occurred. |
| 70 | + |
| 71 | +#### Change `fieldArgs.get` to `fieldArgs.getRaw` |
| 72 | + |
| 73 | +Because we've removed `inputPlan`, the `fieldArgs.get(key)` method is no more; |
| 74 | +instead use `fieldArgs.getRaw(key)` which is equivalent unless the inputs had |
| 75 | +plans (which they cannot any more). |
| 76 | + |
| 77 | +#### Converting `applyPlan` and `inputPlan` |
| 78 | + |
| 79 | +If your input object fields did have plan resolvers then instead of having |
| 80 | +Grafast automatically call them on each and every input field recursively at |
| 81 | +plan-time, we now have the `applyInput` and `bakedInput` steps that represent |
| 82 | +runtime application or transform of these inputs recursively via a single step |
| 83 | +in our plan diagram. |
| 84 | + |
| 85 | +We've managed to make this new runtime system very similar in shape to the old |
| 86 | +plan-time system, so PostGraphile plugins don't need to change much — this was |
| 87 | +largely enabled by how closely we managed to get the Grafast plan syntax to the |
| 88 | +syntax of code you would normally write at runtime. The first change is to |
| 89 | +rename `applyPlan` to `apply`, and `inputPlan` to `baked`. From there, your code |
| 90 | +might just work straight away, or it might need some more small tweaks (e.g. |
| 91 | +`fieldArgs` is no longer present, it's been replaced with simply the runtime |
| 92 | +value of the current field). |
| 93 | + |
| 94 | +#### No more `$step.eval*()` |
| 95 | + |
| 96 | +The eval methods are now marked as internal so you will get TypeScript errors if |
| 97 | +you try and use them. They will likely be removed at some point after release, |
| 98 | +so you should be sure to migrate away from using them at your earliest |
| 99 | +opportunity. But you weren't using them anyway… right? |
| 100 | + |
| 101 | +#### ExecutableStep renamed to Step |
| 102 | + |
| 103 | +This one is more cosmetic… |
| 104 | + |
| 105 | +Since we no longer have plan resolvers deep in inputs, we no longer have the |
| 106 | +`ModifierStep` system that was used for managing them (it's been replaced with |
| 107 | +`Modifier` which happens at runtime). Since we no longer have ModifierStep, we |
| 108 | +no longer need `BaseStep` to be separate from and inherited by `ExecutableStep`, |
| 109 | +so we've merged them. Since this is the base class for _all_ steps now, we've |
| 110 | +renamed it to simply `Step`. |
| 111 | + |
| 112 | +_We have kept an <code>ExecutableStep</code> export for backwards |
| 113 | +compatibility._ |
| 114 | + |
| 115 | +## PostGraphile changes |
| 116 | + |
| 117 | +In addition to the changes above that impact everything that uses Gra*fast*, |
| 118 | +here are some of the changes that specifically impact PostGraphile users. |
| 119 | + |
| 120 | +### SQL generation moved to runtime |
| 121 | + |
| 122 | +PostGraphile's various SQL-running steps like PgSelectStep now build their |
| 123 | +queries at runtime rather than plantime. They use the "builder" pattern, where |
| 124 | +much of the SQL query can be established at plan-time, but final tweaks can be |
| 125 | +applied at run-time (register tweaks via the `$pgSelect.apply($callback)` |
| 126 | +method) before the query is built. |
| 127 | + |
| 128 | +### SQL efficiency increased |
| 129 | + |
| 130 | +Since we have more information at run-time, our SQL queries were able to become |
| 131 | +even simpler, 10% smaller on average across our test suite! This nets us a |
| 132 | +modest performance improvement inside PostgreSQL, but the shift to runtime does |
| 133 | +cost us a little performance in the JS layer since queries now need to be built |
| 134 | +for every request, rather than once per plan. We're happy with this tradeoff; |
| 135 | +one of the core goals of PostGraphile V5 (and the motivation for Grafast in the |
| 136 | +first place) was to shift load from the PostgreSQL layer (which is non-trivial |
| 137 | +to scale) to the Node.js layer (which is easy to scale horizontally). |
| 138 | + |
| 139 | +### Postgres Arrays now parse 5x faster |
| 140 | + |
| 141 | +I've also [backported](https://github.com/bendrucker/postgres-array/pull/19) |
| 142 | +these [fixes](https://github.com/bendrucker/postgres-array/pull/20) into the |
| 143 | +`postgres-array` npm module for everyone that uses `pg` to benefit from. |
| 144 | + |
| 145 | +### Easier to write SQL fragments |
| 146 | + |
| 147 | +Added a new feature to `pg-sql2` that allows us to handle non-SQL parameter |
| 148 | +embeds with custom code, making it easier to write custom SQL, e.g. if a value |
| 149 | +is already coming from SQL you can embed it directly without having to invoke |
| 150 | +placeholder: |
| 151 | + |
| 152 | +```diff |
| 153 | + const $fooId = $foo.get('id'); |
| 154 | +-$pgSelect.where(sql`foo_id = ${$pgSelect.placeholder($fooId)}`); |
| 155 | ++$pgSelect.where(sql`foo_id = ${$fooId}`); |
| 156 | +``` |
| 157 | + |
| 158 | +We've also added the ability to embed dynamic SQL fragments that can be |
| 159 | +dependent on runtime values (these values must be unary, i.e. they must come |
| 160 | +from GraphQL field arguments or derivatives thereof): |
| 161 | + |
| 162 | +```ts |
| 163 | +const $includeArchived = fieldArgs.getRaw("includeArchived"); |
| 164 | +const $condition = lambda($includeArchived, includeArchived => |
| 165 | + includeArchived ? sql.true : sql`is_archived is false` |
| 166 | +); |
| 167 | +$pgSelect.where($condition); |
| 168 | +``` |
| 169 | + |
| 170 | +## Additional changes |
| 171 | + |
| 172 | +### makeGrafastSchema |
| 173 | + |
| 174 | +- 🚨The structure of `makeGrafastSchema` as it relates to arguments and input |
| 175 | + object fields has changed a little; use TypeScript to guide you. I'm hoping |
| 176 | + this is the last change of its kind before release. |
| 177 | +- New shortcuts added for argument `applyPlan()` and input field `apply()` |
| 178 | + methods. |
| 179 | +- Trimmed a load of unnecessary exported code, such as empty objects and field |
| 180 | + resolvers that do the same as the default field resolver. |
| 181 | +- Fix bug in `makeGrafastSchema` that fails to build schema sometimes if a field |
| 182 | + uses a function shortcut rather than object definition. |
| 183 | +- Fix bug in `makeGrafastSchema` that sometimes doesn't allow defining input |
| 184 | + objects |
| 185 | + |
| 186 | +🚨 If you use `graphile-export` to export your schema as executable code, be |
| 187 | +sure to regenerate your schemas as the old generated code could be |
| 188 | +misinterpreted by the new `makeGrafastSchema`. |
| 189 | + |
| 190 | +### graphile-export |
| 191 | + |
| 192 | +- Massively improved the executable code output from `graphile-export` in |
| 193 | + combination with the changes to `makeGrafastSchema` above. |
| 194 | +- PostGraphile's "kitchen sink" schema export code now outputs 37KLOC rather |
| 195 | + than 47KLOC - a significant reduction in complexity! |
| 196 | + |
| 197 | +### Improved plan diagrams |
| 198 | + |
| 199 | +- Plan diagrams now reveal (via `@s` text) if a step is meant to be streamed. |
| 200 | +- Constant steps improved. |
| 201 | +- `Object: null prototype` simplified to `§` in output. |
| 202 | +- Hoist steps during `optimize` phase. |
| 203 | +- We no longer render dependencies on the `undefined` constant, because it's |
| 204 | + messy and doesn't add value |
| 205 | +- We group when there are multiple dependencies to the same step from the same |
| 206 | + step, and label the line with the count instead. |
| 207 | + |
| 208 | +### Step classes |
| 209 | + |
| 210 | +When writing your own step classes: |
| 211 | + |
| 212 | +- `ExecutionValue` has gained a new `.unaryValue()` method that returns the |
| 213 | + unary value for unary execution values, and throws an error for non-unary |
| 214 | + execution values. This is much safer than the previous `.at(0)` trick which |
| 215 | + did not assert that you were actually dealing with a unary execution value. |
| 216 | +- If you were using `@stream` (incremental delivery) and had written your own |
| 217 | + `Step` class with stream support, first of all: amazing! Please let me know |
| 218 | + you did that (_via email or [Discord](https://discord.gg/graphile)_)! |
| 219 | + Secondly, you'll need to either rename your `stream` function to `execute` or |
| 220 | + merge its code into your existing `execute` method if you have one. It turns |
| 221 | + out there wasn't much point in separating them, and you can confer a lot of |
| 222 | + benefit from merging them. |
| 223 | + |
| 224 | +### Other Gra*fast* improvements |
| 225 | + |
| 226 | +- Compatible mutation operations can now complete synchronously via |
| 227 | + `grafastSync()` |
| 228 | +- Fixes bug in input objects where keys that weren't set would still be present |
| 229 | + with value `undefined` |
| 230 | +- Fix bug in step caching relating to polymorphism |
| 231 | +- New `items()` conventional method for extracting the items from a collection |
| 232 | + (makes for easier compatibility with connections) |
| 233 | +- Error handling improved |
| 234 | +- Lists improved - especially error handling and deduplication logic; as well as |
| 235 | + allowing returning connection-capable steps in list positions |
| 236 | +- Optimization to Gra*fast*'s internal execution values, which are used heavily |
| 237 | + in hot paths. |
| 238 | + |
| 239 | +### Thank you Sponsors |
| 240 | + |
| 241 | +Gra*fast* and PostGraphile are crowd-funded open-source software, they rely on |
| 242 | +crowd-sourced funding from individuals and companies to keep advancing. |
| 243 | + |
| 244 | +If your company benefits from Gra*fast*, PostGraphile or the wider Graphile |
| 245 | +suite, you should consider asking them to fund our work. By significantly |
| 246 | +reducing the amount of work needed to achieve business goals and reducing |
| 247 | +running costs, Graphile's software results in huge time and money savings for |
| 248 | +users. We encourage companies to contribute a portion of these savings back, |
| 249 | +enabling the projects to advance more rapidly, and result in even greater |
| 250 | +savings for your company. |
| 251 | +[Find out more about sponsorship here on our website](/sponsor/). |
| 252 | + |
| 253 | +<div class="flex flex-wrap justify-around"> |
| 254 | +<img alt="Cartoon Benjie and Jem send cartoon hearts up into the sky" src="/images/news/postgraphile-thankyou.svg" style="max-height: 300px" /> |
| 255 | +</div> |
0 commit comments