Skip to content

Improve perf of separateOperations #710

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

Merged
merged 3 commits into from
Feb 7, 2017
Merged

Improve perf of separateOperations #710

merged 3 commits into from
Feb 7, 2017

Conversation

schaitoff
Copy link

The separateOperations function does a pass over all definitions of a document for each operation in the document. This is painful for big documents. Since the function already visits each definition node, we can cache the visited nodes. Then instead of passing over all top-level definitions in the document and removing those not in an operation's depgraph, we can pass over the (often much smaller) list of definition names in the depgraph and retrieve the definitions by name from the cache.

Ran flow check and separateOperations-test.js

},
FragmentDefinition(node) {
fromName = node.name.value;
definitions[fromName] = { idx, node };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's currently nothing to guarantee that an operation and fragment definition will always have distinct names. It would be safer to have separate operationDefinitions and fragmentDefinitions maps

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - lookups into this map are happening only for fragments, so you only need these

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I didn't realize definition names were not global.

@leebyron
Copy link
Contributor

leebyron commented Feb 6, 2017

Awesome! I'll do a more careful review but I'm curious if you were able to collect any performance numbers before and after this change?

@@ -47,23 +52,32 @@ export function separateOperations(
// For each operation, produce a new synthesized AST which includes only what
// is necessary for completing that operation.
const separatedDocumentASTs = Object.create(null);
operations.forEach(operation => {
const operationName = opName(operation);
const operationNames = Object.keys(definitions).filter(defName =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving the existing list of operations as it was would remove the need to do a second pass through all the definitions

@schaitoff
Copy link
Author

The change in performance was significant. I was testing on a document with 1796 operations and 3818 fragments. Before separateOperations took 2.8 seconds. Afterward it took 0.4 seconds.

That inner loop of 5000+ iterations dropped to about 60 iterations on average (# of transitive fragments per query in my doc).

@leebyron
Copy link
Contributor

leebyron commented Feb 7, 2017

7x improvement is nothing to sneeze at! Nice work.

I just pushed on an alteration that uses Map instead of {} to access indices to avoid any naming collisions.

@leebyron leebyron merged commit b948b25 into graphql:master Feb 7, 2017
@schaitoff
Copy link
Author

Awesome!

@ericclemmons
Copy link

Will this be in v0.9.2? I stumbled upon this from apollographql/apollo-server#309 in the hopes this improves performance with our large documents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants