Enforcing delivery order of payloads #17
Replies: 5 comments 11 replies
-
from @michaelstaib https://discord.com/channels/625400653321076807/862834364718514196/908330498441511033
|
Beta Was this translation helpful? Give feedback.
-
graphql-executor, the customizable graphql executor based on graphql-js, now supports incremental delivery with preservation of payload order for defer (a) and stream (d), allowing early send with nested non dependent defer (b) but not for resolution of field from another branch as in (c). |
Beta Was this translation helpful? Give feedback.
-
A related question: does this also mean that for Payload 1: {
"data": {
"id": "1"
},
"path": [
"products",
1
],
"hasNext": true
} Payload 2: {
"data": {
"id": "0"
},
"path": [
"products",
0
],
"hasNext": false
} (receiving |
Beta Was this translation helpful? Give feedback.
-
HI! I am experimenting with
|
Beta Was this translation helpful? Give feedback.
-
Thanks a lot! However with this I'm also getting |
Beta Was this translation helpful? Give feedback.
-
Context
A naive implementation of defer/stream could simply race all available upcoming payloads and return the first one that is ready. This could lead to results where a payload is sent with a path that references a field the client has not received yet.
Decision
It was discussed in the 2021-07-01 WG meeting and 2021-12-02 WG meeting and the group agreed that responses like this must be ordered to ensure payload paths do not reference fields that have not been sent yet. Similarly, a streamed field result must not be sent before a result with a lower index.
Discussed again in the 2022-02-03 WG meeting and there was general agreement on the approach outlined below. This feels like the most conservative approach. There may be scenarios where we may be trading performance for making client digestion of payloads easier, but we could potentially look at that later.
Implementation
tl;dr: When executing a deferred fragment or streamed field, a reference will be passed around. Any downstream deferred fragments or streamed fields must wait for the parent to complete its execution before it can be completed itself. Additionally, subsequent items in streamed fields must wait for the prior item to complete its execution.
The GraphQL spec defines execution of a selection set via the
ExecuteSelectionSet
algorithm. InExecuteSelectionSet
, a group of fields are gathered using theCollectFields
algorithm, before executing each collected field using theExecuteField
andCompleteValue
algorithms.Defer
Our proposal updates the
CollectFields
algorithm to detect fragments with@defer
and separately execute their selection set by callingExecuteSelectionSet
with the selection set from the deferred fragment. Additionally a reference to the deferred fragment will now be passed toExecuteSelectionSet
,ExecuteField
, andCompleteValue
.If a new deferred fragment is detected while executing the selection set from a previously detected deferred fragment, the execution of this new deferred fragment must not complete until the execution of the previous deferred fragment is complete.
This ensures results are delivered in a hierarchal order. It also ensures that it is not possible to receive a payload that contains a path reference to an object that has not yet been delivered.
Stream
Our proposal also updates the
CompleteValue
algorithm to detect list fields with the@stream
directive. Values yielded by the underlying iterator on a streamed field are resolved with an additional call toCompleteValue
.If a deferred fragment reference is passed to
CompleteValue
, the first streamed value must not complete its execution until the execution from the deferred fragment reference is complete. Similarly, all subsequent streamed values must not complete their execution until the prior streamed value is complete.This ensures the first streamed result is not delivered before the payload containing the list the streamed result belongs to. It also ensures that any subsequent streamed results are not delivered before prior streamed results.
Example A
Let's say that that the
person.homeworld
field take longer to resolve than theperson.species
field. The result would be:This means the client would receive a payload with the path
["person", "species"]
before the species field is returned to the client.In this case, a reference to the execution of
...TopFragment @defer(label: "DeferTop")
will be passed toExecuteSelectionSet
andCollectFields
when...NestedFragment @defer(label: "DeferNested")
is encountered. The execution of...NestedFragment @defer(label: "DeferNested")
will use this reference to ensure it is not resolved untilTopFragment
is complete.Example B: deferred fragments that do not have a direct tree dependency
In this case, if
NestedFragment
is ready beforeTopFragment
, it will not wait forTopFragment
to be sent first. WhenCollectFields
analyzes the selection set ofperson(personID: "3")
, it will recursively traverse the fragment spreads. Therefore...TopFragment @defer(label: "DeferTop")
and...NestedFragment @defer(label: "DeferNested")
can be resolved in parallel.Example C - parent dependency can be resolved via another fragment
What if
NestedFragment
is ready beforeTopFragment
, but afterUnrelatedTopFragment
? In this case,CollectFields
will trigger execution of...TopFragment @defer(label: "DeferTop")
and...UnrelatedTopFragment @defer(label: "DeferUnrelatedTop")
in parallel, since they are siblings.CollectFields
will encounter...NestedFragment @defer(label: "DeferNested")
only while executing...TopFragment @defer(label: "DeferTop")
, therefore the payload from this fragment will be its only dependency.Example D - Stream payloads should be sent after parent deferred fragment
In this case, the field
friends @stream(initialCount: 0)
will be executed byCollectFields
originating from a call toExecuteSelectionSet
for...DeferFragment @defer
. The first result from this streamed field must be returned after the payload for...DeferFragment @defer
.Example E - Stream payloads must not be sent out of order
Subsequent payloads from a
@stream
directive must not be sent before prior payloads from the same stream.Status
Beta Was this translation helpful? Give feedback.
All reactions