[graphql-js] Yielding batches of results in resolver functions #40
Replies: 4 comments 7 replies
-
(Moved from #38) I think there will be many cases where underlying api yields single values and many other cases where the underlying api yields lists of values. One example of a database api that yields lists of values is the DynamoDB paginateQuery API. Option A: If we choose to yield single values, servers will need to convert apis the yield lists to yield each individual value. Option B: If we choose to yield lists, servers will need to convert apis that yield individual values to wrap each value in a list before yielding. With Option A, we remove the ability for the GraphQL execution to synchronously process more than one value at a time. While the resolver has access to multiple values, each value will have the overhead of an event loop tick added before the GraphQL execution can start processing it. Option B doesn't introduce any overhead, but requires more code for what might be the simpler or more common use-case. I think the tradeoffs for Options B are worth it. Some number of users will have to adapt their resolver code to fit whatever pattern we decide on, so it makes sense to me to favor the one that is more efficient and flexible. |
Beta Was this translation helpful? Give feedback.
-
TL;DR: Option C: As option A, but with introduction of "Batch" type. I am a proponent of allowing the return of lists to enable inherent batching - as you rightly point out, the underlying APIs are likely being batched (fetching 10 or 100 rows at a time from the source, for example) so allowing this to be expressed should keep this effiency throughout the stack rather than introducing inefficiency for the sake of simplicity. However, with that being said, I have concerns. My primary concern is that in GraphQL.js the resolver for a field defined to return a list (e.g. One option is to automatically coerce between these formats; currently the GraphQL spec states in CompleteValue() that if the field type is a list type and the returned value is not a collection of values then a field error should be raised, which makes it relatively safe for us to do this. However there's immediate ambiguities for nested lists e.g. The Option B proposal is that the return type for the async iterable is concretely different to that of the regular iterable. It looks like that's safe to do currently (will not break existing servers), but is unintuitive, and it's a divergence from how async iterators work for subscriptions. Doing it has the pains outlined above, however using Option A pushes the problem of batching to a separate layer. Despite it's problems, I still feel option B would be the correct course if we could only follow one of these two courses (system efficiency is more important to me than the issues of iterable/async iterable inconsistency)... but I wonder if there may be a third option somewhere? We could, for example, have a new type called Batch (or similar) and it could be returned as part of lists, iterables, and async iterables and handled the same in all cases. That way we'd have the Option A type, but with this new "Batch" type added everywhere. So: // Pseudocode
type ListResolverResult<T> = Iterable<BatchOrDirect<T>> | AsyncIterable<BatchOrDirect<T>>
type BatchOrDirect<T> = T | Batch<T> |
Beta Was this translation helpful? Give feedback.
-
Does this option unblock the current PR for adding AsyncIterable support (implementing Option A)? graphql/graphql-js#2757 This would allow us to move forward with this feature and defer/stream while separately working out the implementation details of |
Beta Was this translation helpful? Give feedback.
-
Option D: Writers of resolver functions should know whether they are using batching or not. So the signature of the resolver should not be:
I.e., rather than checking at runtime to see whether each entity yielded is an item or array of items, developers should somehow annotate the resolver to indicate whether batching is going to be used. One way this can be done is by using the standard Similarly, for subscriptions: |
Beta Was this translation helpful? Give feedback.
-
Moved from (#38)
Context
graphql-js will be updated to allow returning an AsyncIterable from list field resolvers. This is a prerequisite to supporting the
@stream
directive. What type should this AsyncIterable yield?Option A: Single value:
AsyncIterable<T>
Option B: Lists of values:
AsyncIterable<Iterable<T>>
This was discussed in #38 and at the May 2022 WG meeting. From the working group meeting it was determined that it is desirable to for these resolver functions to yield batches of values.
Decision
Discussed at the May 2022 GraphQL-JS working group and there was general agreement that we should allow both individual results and batches of results. The default will be individual results and the mechanism used to support batching is still to be determined (see comment #40 (comment)). The group agreed that we can safely merge Option A to graphql-js to unblock further progress on defer/stream, and batching support can be championed separately.
References
Beta Was this translation helpful? Give feedback.
All reactions