Optimizing incremental delivery #38
Replies: 8 comments 8 replies
-
@robrichard -- i brought the above up at the most recent graphql-js wg, but it wasn't felt to be the best forum. @IvanGoncharov suggested perhaps breaking out a separate implementer group for defer-stream. I am honestly not scared by releasing as-is and then introducing this later as a breaking change, as breaking changes in my opinion are not The Worst Thing Ever. But, if we could get consensus around this now, I suppose it can't hurt to just go for it. |
Beta Was this translation helpful? Give feedback.
-
Notes after April WG:
|
Beta Was this translation helpful? Give feedback.
-
Additional thoughts on different meanings/motivations batching with incremental delivery. This discussion is only about the first type of batching described below, but it it helpful to try to spell out as much as possible the other types of batching just to disambiguate the discussion. Batching available payloads into a single payloadA first type of batching is about sending multiple payloads to the client at once. If multiple deferred payloads are ready, or if multiple stream payloads are ready -- from the same or even from different streams -- we might as well send everything to the client as we have it, as a list that wraps all of the available payloads. This discussion is about this type of batching, and the format we would use for it, reflected in the return type for yaacovCR/graphql-spec#1: Send all available payloads Besides for performance on the server, it also may potentially optimize battery life on the client in terms of raw number of network requests. It may also optimize performance of the client application just like on the server. Delaying stream results until a "batch" is readyA totally separate meaning of batching is whether results should not be sent to the client at all unless a certain threshold number of results is reached. I prefer to refer to this as "chunking" or "bundling" rather than batching to avoid confusion. This behavior may also potentially save battery life and increase performance, but comes at the cost that the client must wait until a certain chunk threshold has been reached. In the implementation at graphql-executor we provide This type of batching or chunking, because it by definition applies to a single stream, could then include all of those stream results within the I think it's important to emphasize, that it could also be appropriate to utilize this form of batching, but instead of including all of the stream results within the Batching stream results without constraintsAlternatively, a third method of batching might be a combination of aspects of the above. Perhaps one could suggest that even if the first type of batching is used to consolidate deferred payloads and multiple streams, when completed results from the same stream are ready, they should be bundled within a single stream payload. That way, for each set of payloads the client receives, each stream's results would be consolidated. After all, this is information the server has already, and the client will have to iterate through the payloads to group them, and so by encoding this within the response, some small performance benefit may be accrued. The goals of this type of batching are dissimilar to both of the options above, although implementation wise, this might be a special case of the second option above, equivalent to a |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Hello all — thanks again for participating in the above polls. Please share your opinions on the issues so we can all get a sense of the relevant issues. It would be great if we could do some work on this in advance of the next meeting and present thoughts. If anyone is interested in a breakout discussion of the above aspects of incremental delivery, I would love to set something up prior to next wg so that the options could be best presented and resolved at that meeting. |
Beta Was this translation helpful? Give feedback.
-
Perhaps I missed it in all the discussions, but is there a method for including backpressure? What if the server produces too quickly for the client? |
Beta Was this translation helpful? Give feedback.
-
I created two new discussions to separately track the topics being discussed here. |
Beta Was this translation helpful? Give feedback.
-
UPDATE this has been moved to two separate discussion topics:
Please continue discussion there.
From: #32 (comment)
Suggestion
(A) Field resolvers that return an async iterable should return an async iterable of iterables, ie an async iterable that returns chunks of the larger list.
(B) Similarly, the AsyncGenerator returned by GraphQL should return an array of available payloads rather than a single item.
Motivation
If our underlying services have been optimized to produce values in chunks, we should yield values in chunks when possible.
Reference from outside GraphQL: https://medium.com/netscape/async-iterators-these-promises-are-killing-my-performance-4767df03d85b
Timing
The main motivation for introducing this change now is to avoid a breaking change by introducing it later.
Spec change?
A spec change does not seem necessary, but may be advisable. As far as I can tell, the exact implementation around asynchronous execution is not specified, and so these changes, so adding batching to asynchronous handling does not seem to conflict with the spec.
It may be advisable for the spec to directly indicate that the use of batching may be a performance optimization.
Implementation
See yaacovCR/graphql-js#154 for a worked example
Suggested type signature for execute:
Suggested type signature for subscribe
Same as above.
Note that
graphql-executor
does not have a separatesubscribe
function, as subscription operations have been completely integrated into the execution code/algorithm, so the above signature already is the de facto signature withingraphql-executor
for the correspondinggraphql-js
subscribe
function.Note also that a choice was made to simplify the signature for subscriptions, as the signature could have theoretically been:
If a subscription operation does not use defer or stream, it should yield only single ExecutionResults rather than arrays. Even if it does include defer/stream, depending on
initialCount
values and/or other factors, the same operation may sometimes result in a single value or an array. For simplicity, the implementation wraps single values in arrays. The extra wrapping (and later unwrapping) (1) allows subscribe to have the same, simpler signature as query and mutation operations and (2) allows the caller to not have to check to see whether the generator yielded an array or not, as an array is always yielded.Example queries/responses
Defer with fragments
Defer with slow field in initial payload
Stream
Stream with chunks of greater than 1
Stream in correct order even when the first item is slow
Stream in parallel
Stream with asyncIterableList
Stream with asyncIterableList with chunk size > 1
Stream where a non-nullable item returns null
Beta Was this translation helpful? Give feedback.
All reactions