diff --git a/src/data/nav.json b/src/data/nav.json index e930bd424..423cb07c2 100644 --- a/src/data/nav.json +++ b/src/data/nav.json @@ -16,6 +16,11 @@ } ], "pages": [ + { + "to": "/news/20250324-major-grafast-beta/", + "title": "Major Grafast release: beta.21", + "sectionId": "main" + }, { "to": "/news/20230803-postgraphile-v5-beta/", "title": "PostGraphile V5 public beta!", diff --git a/src/news/2025-03-24-major-grafast-beta.md b/src/news/2025-03-24-major-grafast-beta.md new file mode 100644 index 000000000..8373c9e9f --- /dev/null +++ b/src/news/2025-03-24-major-grafast-beta.md @@ -0,0 +1,255 @@ +--- +layout: post +title: "Major Grafast beta: three down, one to go" +date: 2025-03-24T17:00:00Z +path: /news/20250324-major-grafast-beta/ +thumbnail: /images/news/grafast-wordmark-2023.svg +thumbnailAlt: "A developer looks at her monitor while holding a cup of tea." +tags: announcements, releases, grafast, postgraphile +noToc: false + +summary: + "This release contains more than 3 months of work, and is a major step towards + release readiness." +--- + +_Announced 2025-03-24 by Benjie_ + +

+In the first Grafast Working Group, we outlined 4 major issues in Grafast + that needed to be addressed before we could think about general release. With + this release, 3 of these are now complete! +

+ +- ✅⤵️ Global dependencies - solved via "unary" steps +- ✅⤵️ Early exit - solved via "flags" +- ✅🎉 **Eradicating eval - this release!** +- 🤔🔜 Polymorphism + +After 3 months of gruelling work, we're proud to announce that the third of +these, eradicating eval, is now addressed with the launch of +`grafast@0.1.1-beta.21` (used as the core execution engine in +`postgraphile@5.0.0-beta.39`). Let's look into what that has meant. + +## Input evaluation moved to runtime + +Since the beginning, Gra*fast* has had the ability to add plan resolvers not +just to fields, not just to arguments, but also to input object fields +(including those within lists). This made Gra*fast*'s planning really ergonomic +for things like nested filters, which was great for PostGraphile! But it turns +out it's really problematic for certain shapes of input — planning would put +constraints on the variables compatible with the plan, requiring potentially +unlimited numbers of operation plans needing to be built for the same GraphQL +document. Worse: for large input trees involving lists, the number of steps +generated could be overwhelming, resulting in the deduplication phase taking +excessive time. + +One particular user example that could cause 4 minutes of planning time from +just a 100kB input made it clear that we had overreached with using plan +resolvers too deep into inputs; so we've scaled it back so that you can only add +plan resolvers to fields and arguments, you can no longer attach `applyPlan` or +`inputPlan` to input object fields. This was something that we used a lot +internally (hence the huge time investment migrating away!), but very few people +(no-one?) used externally so it was ripe for removal. + +That problematic query that took 4 minutes to plan before? It now takes 1.1ms to +plan, yielding a 200,000x speedup! + +### What does this mean for my codebase? + +Hopefully good things! Please update to the latest `@beta` of all the +PostGraphile and/or Gra*fast* related modules you're using (including plugins) +and for most users everything should work as before, only better. + +I've outlined some of the most common changes you may need to make below, but if +you are impacted by any other changes, please ask for help in the chat — AFAIK +most of the other things that have had significant changes are used by almost +no-one except me, so it doesn't make sense for me to invest time documenting it +here. If you're curious, many items are documented in both the changelogs and +the pull requests where the changes occurred. + +#### Change `fieldArgs.get` to `fieldArgs.getRaw` + +Because we've removed `inputPlan`, the `fieldArgs.get(key)` method is no more; +instead use `fieldArgs.getRaw(key)` which is equivalent unless the inputs had +plans (which they cannot any more). + +#### Converting `applyPlan` and `inputPlan` + +If your input object fields did have plan resolvers then instead of having +Grafast automatically call them on each and every input field recursively at +plan-time, we now have the `applyInput` and `bakedInput` steps that represent +runtime application or transform of these inputs recursively via a single step +in our plan diagram. + +We've managed to make this new runtime system very similar in shape to the old +plan-time system, so PostGraphile plugins don't need to change much — this was +largely enabled by how closely we managed to get the Grafast plan syntax to the +syntax of code you would normally write at runtime. The first change is to +rename `applyPlan` to `apply`, and `inputPlan` to `baked`. From there, your code +might just work straight away, or it might need some more small tweaks (e.g. +`fieldArgs` is no longer present, it's been replaced with simply the runtime +value of the current field). + +#### No more `$step.eval*()` + +The eval methods are now marked as internal so you will get TypeScript errors if +you try and use them. They will likely be removed at some point after release, +so you should be sure to migrate away from using them at your earliest +opportunity. But you weren't using them anyway… right? + +#### ExecutableStep renamed to Step + +This one is more cosmetic… + +Since we no longer have plan resolvers deep in inputs, we no longer have the +`ModifierStep` system that was used for managing them (it's been replaced with +`Modifier` which happens at runtime). Since we no longer have ModifierStep, we +no longer need `BaseStep` to be separate from and inherited by `ExecutableStep`, +so we've merged them. Since this is the base class for _all_ steps now, we've +renamed it to simply `Step`. + +_We have kept an ExecutableStep export for backwards +compatibility._ + +## PostGraphile changes + +In addition to the changes above that impact everything that uses Gra*fast*, +here are some of the changes that specifically impact PostGraphile users. + +### SQL generation moved to runtime + +PostGraphile's various SQL-running steps like PgSelectStep now build their +queries at runtime rather than plantime. They use the "builder" pattern, where +much of the SQL query can be established at plan-time, but final tweaks can be +applied at run-time (register tweaks via the `$pgSelect.apply($callback)` +method) before the query is built. + +### SQL efficiency increased + +Since we have more information at run-time, our SQL queries were able to become +even simpler, 10% smaller on average across our test suite! This nets us a +modest performance improvement inside PostgreSQL, but the shift to runtime does +cost us a little performance in the JS layer since queries now need to be built +for every request, rather than once per plan. We're happy with this tradeoff; +one of the core goals of PostGraphile V5 (and the motivation for Grafast in the +first place) was to shift load from the PostgreSQL layer (which is non-trivial +to scale) to the Node.js layer (which is easy to scale horizontally). + +### Postgres Arrays now parse 5x faster + +I've also [backported](https://github.com/bendrucker/postgres-array/pull/19) +these [fixes](https://github.com/bendrucker/postgres-array/pull/20) into the +`postgres-array` npm module for everyone that uses `pg` to benefit from. + +### Easier to write SQL fragments + +Added a new feature to `pg-sql2` that allows us to handle non-SQL parameter +embeds with custom code, making it easier to write custom SQL, e.g. if a value +is already coming from SQL you can embed it directly without having to invoke +placeholder: + +```diff + const $fooId = $foo.get('id'); +-$pgSelect.where(sql`foo_id = ${$pgSelect.placeholder($fooId)}`); ++$pgSelect.where(sql`foo_id = ${$fooId}`); +``` + +We've also added the ability to embed dynamic SQL fragments that can be +dependent on runtime values (these values must be unary, i.e. they must come +from GraphQL field arguments or derivatives thereof): + +```ts +const $includeArchived = fieldArgs.getRaw("includeArchived"); +const $condition = lambda($includeArchived, includeArchived => + includeArchived ? sql.true : sql`is_archived is false` +); +$pgSelect.where($condition); +``` + +## Additional changes + +### makeGrafastSchema + +- 🚨The structure of `makeGrafastSchema` as it relates to arguments and input + object fields has changed a little; use TypeScript to guide you. I'm hoping + this is the last change of its kind before release. +- New shortcuts added for argument `applyPlan()` and input field `apply()` + methods. +- Trimmed a load of unnecessary exported code, such as empty objects and field + resolvers that do the same as the default field resolver. +- Fix bug in `makeGrafastSchema` that fails to build schema sometimes if a field + uses a function shortcut rather than object definition. +- Fix bug in `makeGrafastSchema` that sometimes doesn't allow defining input + objects + +🚨 If you use `graphile-export` to export your schema as executable code, be +sure to regenerate your schemas as the old generated code could be +misinterpreted by the new `makeGrafastSchema`. + +### graphile-export + +- Massively improved the executable code output from `graphile-export` in + combination with the changes to `makeGrafastSchema` above. +- PostGraphile's "kitchen sink" schema export code now outputs 37KLOC rather + than 47KLOC - a significant reduction in complexity! + +### Improved plan diagrams + +- Plan diagrams now reveal (via `@s` text) if a step is meant to be streamed. +- Constant steps improved. +- `Object: null prototype` simplified to `§` in output. +- Hoist steps during `optimize` phase. +- We no longer render dependencies on the `undefined` constant, because it's + messy and doesn't add value +- We group when there are multiple dependencies to the same step from the same + step, and label the line with the count instead. + +### Step classes + +When writing your own step classes: + +- `ExecutionValue` has gained a new `.unaryValue()` method that returns the + unary value for unary execution values, and throws an error for non-unary + execution values. This is much safer than the previous `.at(0)` trick which + did not assert that you were actually dealing with a unary execution value. +- If you were using `@stream` (incremental delivery) and had written your own + `Step` class with stream support, first of all: amazing! Please let me know + you did that (_via email or [Discord](https://discord.gg/graphile)_)! + Secondly, you'll need to either rename your `stream` function to `execute` or + merge its code into your existing `execute` method if you have one. It turns + out there wasn't much point in separating them, and you can confer a lot of + benefit from merging them. + +### Other Gra*fast* improvements + +- Compatible mutation operations can now complete synchronously via + `grafastSync()` +- Fixes bug in input objects where keys that weren't set would still be present + with value `undefined` +- Fix bug in step caching relating to polymorphism +- New `items()` conventional method for extracting the items from a collection + (makes for easier compatibility with connections) +- Error handling improved +- Lists improved - especially error handling and deduplication logic; as well as + allowing returning connection-capable steps in list positions +- Optimization to Gra*fast*'s internal execution values, which are used heavily + in hot paths. + +### Thank you Sponsors + +Gra*fast* and PostGraphile are crowd-funded open-source software, they rely on +crowd-sourced funding from individuals and companies to keep advancing. + +If your company benefits from Gra*fast*, PostGraphile or the wider Graphile +suite, you should consider asking them to fund our work. By significantly +reducing the amount of work needed to achieve business goals and reducing +running costs, Graphile's software results in huge time and money savings for +users. We encourage companies to contribute a portion of these savings back, +enabling the projects to advance more rapidly, and result in even greater +savings for your company. +[Find out more about sponsorship here on our website](/sponsor/). + +
+Cartoon Benjie and Jem send cartoon hearts up into the sky +
diff --git a/static/images/news/grafast-wordmark-2023.svg b/static/images/news/grafast-wordmark-2023.svg new file mode 100644 index 000000000..ffbc15118 --- /dev/null +++ b/static/images/news/grafast-wordmark-2023.svg @@ -0,0 +1,631 @@ + + + + diff --git a/static/images/news/postgraphile-thankyou.svg b/static/images/news/postgraphile-thankyou.svg index 0b76b155c..5e34b93e2 100644 --- a/static/images/news/postgraphile-thankyou.svg +++ b/static/images/news/postgraphile-thankyou.svg @@ -7,7 +7,7 @@ version="1.1" id="svg6581" sodipodi:docname="postgraphile-thankyou.svg" - inkscape:version="1.2.1 (9c6d41e4, 2022-07-14)" + inkscape:version="1.4 (e7c3feb1, 2024-10-09)" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" @@ -24,13 +24,13 @@ inkscape:pagecheckerboard="0" inkscape:deskcolor="#d1d1d1" showgrid="false" - inkscape:zoom="1.2528221" - inkscape:cx="153.254" - inkscape:cy="284.55756" - inkscape:window-width="1362" + inkscape:zoom="0.92287575" + inkscape:cx="152.7833" + inkscape:cy="283.8952" + inkscape:window-width="1376" inkscape:window-height="935" inkscape:window-x="1684" - inkscape:window-y="444" + inkscape:window-y="416" inkscape:window-maximized="0" inkscape:current-layer="svg6581" /> + style="fill:#7c2dd3;fill-opacity:1" /> + style="fill:#7c2dd3;fill-opacity:1" /> + style="fill:#7c2dd3;fill-opacity:1" />