-
Notifications
You must be signed in to change notification settings - Fork 2.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WIP: Request batching #266
Conversation
I think keeping the polling scheduler separate from batching makes perfect sense. It would still be possible to do all the batching in the network interface in that case, even with "dumb" batching, i.e. merging root fields. Since the network interface is the thing that talks to the server, it should be the place where we decide whether and how to batch. If the server supports batching over the transport, then that should be used. If not, the queries could be merged at the root field. There are some advantages and some disadvantages to the root field merging type of batching, but I think we should implement it first, because it will take a while until servers can support any other batching meaningfully. I think the query manager doesn't need to do the actual batching, but maybe it makes sense if it is able to submit multiple queries to the network interface at once by calling The network interface should not decide how long to queue queries if it does batching. That would be up to the query manager or the scheduler. The network interface should only batch queries that it receives in the same tick, be it through one call of |
That makes a lot of sense to me and that seems to be pretty close to the current design. So, I guess the next steps are:
|
queryId: string; | ||
}; | ||
|
||
// QueryScheduler takes a operates on a queue of QueryFetchRequests. It polls and checks this queue |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo?
…ked network interface
…ents to work within query merging
…serialized string structure
… spreads within result unpacking
This PR is now at a point where the query merging is working correctly (i.e. you can take any network interface and turn it into a network interface that does query merging), the query scheduler class has most of the methods it needs implemented and @helfer Do you think we could get this to merge quality and then I'll implement all the gluing that will make the scheduler operate correctly? I think that way the changes in the base classes and methods (e.g. |
@@ -283,7 +283,7 @@ export class QueryManager { | |||
} | |||
|
|||
// Sends several queries batched together into one fetch over the transport. | |||
public fetchBatchedQueries(fetchRequests: QueryFetchRequest[]): Promise<GraphQLResult[]> { | |||
public fetchManyQueries(fetchRequests: QueryFetchRequest[]): Promise<GraphQLResult[]> { | |||
// There are three types of promises used here. | |||
// - fillPromise: resolved once we have transformed each of the queries in |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
'transformed' is repeated by mistake, I think.
@@ -0,0 +1,137 @@ | |||
import { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think QueryScheduler.ts would be a better name for this file.
Couple of things to consider (just doing a braindump here):
After an initial look, I think you should break out queryMerging into a separate PR. That will make this one much more manageable. Luckily, they're also logically separate. |
Remove wording about "just like above"
Goal
Implement request batching so that multiple requests can be sent to the server in one round trip.
Currently implemented
It is possible to send the queries to the server as a batch of requests (i.e. an array of request objects rather than a single request object) through
QueryManager
. In addition, there is aQueryScheduler
that consumes a queue of queries, repeating over a particular interval. It has been implemented so that at most one query is in flight at a time per polling query (mentioned as an issue within Issue #248).In progress
QueryScheduler
toQueryManager
so that instead of callingfetchQuery
, queries are queued to theQueryScheduler
which then consumes the queue on some interval.Architecture
Before this PR, all of the query fetching logic lived in
fetchQuery
. This logic consisted of three steps:this.networkInterface
then
on the promise returned bythis.networkInterface
, store the results in the Redux storeThis PR introduces a generalized version of
fetchQuery
that can operate over anyNetworkInterface
(see step 2 above) and calls itfetchQueryOverInterface
. It also introduces a methodfetchBatchedQueries
to QueryManager that usesfetchQueryOverInterface
in order to group together queries and send them over a particularnetworkInterface
. Internally,fetchBatchedQueries
callsbatchedQuery
on a network interface and lets the network interface handle the actual request formation. This allows us to batch arbitrary queries together.The time-dependent stuff lives inside another class called
QueryScheduler
which operates on a queue of queries that it may batch together if possible.Reasoning for Architecture
Had a quick conversation with @helfer that made me think more about the architectural decisions behind this PR. There are a couple of reasons why it makes sense to add
fetchBatchedQueries
to QueryManager and keepQueryScheduler
separated:QueryManager exists as a mechanism to allow us to submit queries over the network interface. The polling interval on which we decide to batch queries may change depending on the polling queries currently in play. For example, if we used to have only 1 second polling queries and suddenly add three that query every 50ms, it doesn't make sense to batch on the same polling interval that we started out with. Since this information is naturally available to
QueryManager
(not the network interface, etc.) andQueryScheduler
, it makes sense for them to have control as to when queries are batched together.The
ApolloClient
class will still be used to callquery
as is. The decision of whether or not to batch will be available to the application developer but the actual batching will happen behind the scenes (i.e. the app. developer shouldn't need to callfetchBatchedQueries
- the batching will "just happen").I can imagine instances where two related queries could be batched together and we have promises for both queries that may be resolved at two different times. If we expose batching independently of the polling mechanism, we could (in the future) expose a way to batch these queries as well.
There are, however, downsides to using this approach that @helfer pointed out. Specifically, if we were to build a network interface that simply batched the queries together on some interval, then the QueryManager would be free of dealing with batching. Secondly, the network interface should be able to decide whether or not it can batch (e.g. batching makes less sense on WebSockets). However, this second point is resolved by the network interface hinting whether or not it will support batching.
Given the points above and the benefits we get by separating the batching and polling (pt. 1 definitely seems the most important to me), I think we should go for the architecture currently implemented. Thoughts?
TODO: