Skip to content
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

Fix fragment reuse in subgraph fetches #1911

Merged
merged 2 commits into from
Jul 21, 2022

Conversation

pcmanus
Copy link
Contributor

@pcmanus pcmanus commented Jun 9, 2022

The query planner has code originally meant to reuse fragments from the
user query in subgraph fetches when it made sense. Unfortunately that
code wasn't triggered at all due to the query planner not properly
capturing those fragments. This ticket fixes this issue, adds more
testing and improves a few related issues.

Fixes #1894

@pcmanus pcmanus self-assigned this Jun 9, 2022
@netlify
Copy link

netlify bot commented Jun 9, 2022

👷 Deploy request for apollo-federation-docs pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit f6f8a52

@codesandbox-ci
Copy link

codesandbox-ci bot commented Jun 9, 2022

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

// cases we won't even reach that point either because there is no fragment, or none will have been
// optimized away so we'll exit above). We can optimize later if this show up in profiling though.
//
// Also note `toDeoptimize` will always contains the unused fragments, which will allow `expandFragments`
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to reviewers: this comment is the reason of the change in this method. Essentially, it removes the prior minUsagesToOptimize > condition so expandFragments can "clean up" unused fragments in all cases.

@@ -648,10 +676,6 @@ export class SelectionSet extends Freezable<SelectionSet> {
}

collectUsedFragmentNames(collector: Map<string, number>) {
if (!this.fragments) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note to reviewers: that condition (and the same below) was currently incorrect because this.fragments is not always set at all the levels of a SelectionSet. We could probably change this and using this.fragments is set at all the levels, but that's a bit easy to get wrong so it feels that not relying on that was less fragile.

@pcmanus
Copy link
Contributor Author

pcmanus commented Jun 13, 2022

Pushed additional commits to introduce a query planner/gateway option to opt-out of reusing fragments in subgraph queries. Both because that de-risk things a bit in the sense that this give a opt-out if this patch goes wrong for someone for some reason, but also because in general this option can be a tad of a tradeoff in that even if it can saves you from potentially super large subgraph queries, it also takes some cycles to look if fragments can be reused or not.

As of those commit, I still left the reusing of fragments as the default, and the option is to opt-out, because I believe query plans are usually well cached so that time-to-compute-a-plan is not absolutely critical for most users (and so, by default, avoiding the risk of subgraph query size getting out of hand feels like a better default that optimising every ms out of the plan computation). I get that it's a judgment and I'm not beyond discussing it.

Copy link
Contributor

@benweatherman benweatherman left a comment

Choose a reason for hiding this comment

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

Using some real-world queries and schemas, I see a pretty drastic slowdown of query planning. I know we've talked previously that we don't necessarily wanna optimize for "query plans per second" or anything like that, but I wonder if we should do some more digging on this before we're ready to merge. I don't have a good sense for "yeah this will slow down query planning, but it's still better because the request size to subgraphs is much smaller" so thought I'd start the convo.

EDIT: I have some granular results for said queries & schemas; would it be helpful to look through those results to see if there are some common patterns? Maybe it would also be helpful to look at query plan size?

EDIT EDIT: We went through the results! Coupled with the other performance changes that have landed recently, these fragment reuse changes really improve things. Thanks for the diligence working on this!

query-planner-js/src/index.ts Outdated Show resolved Hide resolved
query-planner-js/src/buildPlan.ts Outdated Show resolved Hide resolved
The query planner has code originally meant to reuse fragments from the
user query in subgraph fetches when it made sense. Unfortunately that
code wasn't triggered at all due to the query planner not properly
capturing those fragments. This ticket fixes this issue, adds more
testing and improves a few related issues.

Fixes apollographql#1894
@benweatherman benweatherman enabled auto-merge (squash) July 21, 2022 20:21
Copy link
Contributor

@benweatherman benweatherman left a comment

Choose a reason for hiding this comment

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

🐐

@benweatherman benweatherman merged commit f123b72 into apollographql:main Jul 21, 2022
pcmanus pushed a commit to pcmanus/federation that referenced this pull request Aug 25, 2022
The code from apollographql#1911 ensures that named fragments from the user query are
reused in subgraph query "when appropriate", but there were some code
path where a fragment that was reused (correctly) could end up be
"re-expanded" (due to an existing method not handling spreads properly).

The resulting subgraph query ended up with the fragment definition
existing but never being used, which is invalid in graphQL.

This patch ensures that a reused fragment is not mistakenly re-expanded
by the code (and thus avoids a query with unused fragments).

Fixes apollographql#2092
pcmanus pushed a commit to pcmanus/federation that referenced this pull request Aug 26, 2022
The code from apollographql#1911 ensures that named fragments from the user query are
reused in subgraph query "when appropriate", but there were some code
path where a fragment that was reused (correctly) could end up be
"re-expanded" (due to an existing method not handling spreads properly).

The resulting subgraph query ended up with the fragment definition
existing but never being used, which is invalid in graphQL.

This patch ensures that a reused fragment is not mistakenly re-expanded
by the code (and thus avoids a query with unused fragments).

Fixes apollographql#2092
pcmanus pushed a commit that referenced this pull request Aug 26, 2022
The code from #1911 ensures that named fragments from the user query are
reused in subgraph query "when appropriate", but there were some code
path where a fragment that was reused (correctly) could end up be
"re-expanded" (due to an existing method not handling spreads properly).

The resulting subgraph query ended up with the fragment definition
existing but never being used, which is invalid in graphQL.

This patch ensures that a reused fragment is not mistakenly re-expanded
by the code (and thus avoids a query with unused fragments).

Fixes #2092
pcmanus pushed a commit to pcmanus/federation that referenced this pull request Sep 2, 2022
The patch for apollographql#1911 introduced an issue where directives applied to a
fragment spread were lost when the named fragments was reused. This
commit fixes it by ensuring we properly copy those directives.

Fixes apollographql#2124
pcmanus pushed a commit that referenced this pull request Sep 6, 2022
The patch for #1911 introduced an issue where directives applied to a
fragment spread were lost when the named fragments was reused. This
commit fixes it by ensuring we properly copy those directives.

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

Successfully merging this pull request may close these issues.

Fragments are not reused in query plans creating large request sizes to subgraphs
2 participants