-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Event loop blocked for large responses in completeListValue #2262
Comments
@lenolib Thanks for the detailed description 👍 Can you achieve the same by returning an async generator instead of an Array? graphql-js/src/execution/__tests__/lists-test.js Lines 61 to 72 in 52157dc
So we can consider also supporting async generators as a more generic solution. |
@lenolib Thanks for detail investigation 👍 Currently, we support only function listDelayWrapper(resolver) {
return (source, args, context, info) => {
const result = resolver(source, args, context, info);
return async function*() {
const t0 = new Date().getTime();
for (const [idx, item] of result.entries()) {
// Check every Nth item (e.g. 20th) if the elapsed time is larger than 50 ms.
// If so, break and divide work into chunks using setImmediate
if (idx % 20 === 0 && idx > 0 && new Date().getTime() - t0 > 50) {
await new Promise((resolve) => setImmediate(resolve))
}
yield item;
}
};
};
} BTW. Here is a good example of async generators: https://javascript.info/async-iterators-generators#async-generators |
Hi, is there any update on this issue? We see the same behaviour on our project. Would be nice if there could be an official fix. |
Same thing, we sometimes have queries that take 30+ seconds, and we are suffering from this as well |
Any update on this? Seems like it would fix a problem I'm facing as well |
This keeps affecting my production servers :/ |
In case it is useful for anyone, I've updated my monkey patch to work with the latest version. Can be found here: |
This is also a huge problem for us - we're not even working with giant lists of entities, just deeply nested, and a list of 300 entities can block the event loop of upwards of 10 seconds while it resolves. |
We had a similar issue due to nested objects. Currently using v17.0.0-alpha.2 as there's no official 17.x release yet. I rewrote some of the code for readability from Ivan's comment above which solved it for us. Putting it here if anyone wants to try it out: const MAX_EVENTLOOP_BLOCK_TIME_IN_MS = 50
const waitForNextEventCycle = () => new Promise(resolve => setImmediate(resolve))
function listDelayWrapper(resolver) {
return async function* (source, args, context, info) {
const startTime = new Date().getTime()
const resultArray = await resolver(source, args, context, info)
for (const [idx, item] of resultArray.entries()) {
const is20thElement = idx % 20 === 0 && idx > 0
const currentTime = new Date().getTime()
const deltaFromStartTime = currentTime - startTime
if (is20thElement && deltaFromStartTime > MAX_EVENTLOOP_BLOCK_TIME_IN_MS) {
await waitForNextEventCycle()
}
yield item
}
}
} Then use it like this: resolve: listDelayWrapper(whatever function was here before) |
It is, given v17 is still in alpha 😊 I have a TS version of your gist, I will share it once I'm sure it works. I also updated it to the latest v16 (just minor changes re-using the helper method they now use).
Hey @lenolib, where do you actually use this last line? For instance, we are using Apollo server, I'm not sure to understand where to set/use rewiredExecute. I thought Am I missing something here? |
@philippemiguet I'm afraid we gave up using the patching above anymore after we moved to graphql-yoga. I remember the library we used before allowed us specify the execute function when setting up the server: https://github.com/graphql-community/koa-graphql |
Thanks @lenolib - It seems our best shot is using GraphQL v17.alpha with an async generator as Torhgal suggests. Out of curiosity, how did you end up solving the problem on your side? |
Closing as resolved in v17 alpha. |
We have an application where we regularly respond with object lists of multiple thousand objects. Completing the list containing these objects can take several seconds, during which the event loop is busy and the server non-responsive, which is far from ideal.
The problematic forEach call in completeListValue here:
https://github.com/graphql/graphql-js/blob/master/src/execution/execute.js#L911
Would it be interesting to divide this work into smaller synchronous chunks of work in order to return to the event loop more frequently?
I have made a working solution below that may be used by anyone who has the same problem and are fine with monkey-patching inside the execute module.
The chunked implementation only starts using chunks if the completion time goes above a given time-threshold (e.g. 50 ms) and uses a variable chunk size in order to minimize overhead.
Profiles before and after chunkification:
The text was updated successfully, but these errors were encountered: