Skip to content

Commit 8978f54

Browse files
committed
Beta2 release post
1 parent ef1fc11 commit 8978f54

File tree

1 file changed

+239
-0
lines changed

1 file changed

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

0 commit comments

Comments
 (0)