-
Notifications
You must be signed in to change notification settings - Fork 254
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
Add new generateQueryFragments
option to query planner config
#2958
Add new generateQueryFragments
option to query planner config
#2958
Conversation
👷 Deploy request for apollo-federation-docs pending review.Visit the deploys page to approve it
|
🦋 Changeset detectedLatest commit: e8560f8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
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. |
56ddc4a
to
b511cf6
Compare
generateQueryFragments
option to query planner config
a4d91d6
to
159d92c
Compare
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.
Spoke to Trevor about the PR, had some suggestions there but will summarize them here:
- The approach we're taking where "every eligible inline fragment gets replaced with a fragment spread" is okay for now, given that (1) this is feature flagged, and (2) there are some users who just flat-out need the compression above all else, and soon. But it's a terrible choice for readability, and there's extra overhead from the unnecessary fragment indirection (for processing and validation), so I'm going to write out a design for a separate algorithm some time next week. We can take that as a follow-up.
- Using the stringified selection set doesn't quite work, since we don't care about the order of various things (selections, directives, arguments, object value fields). We could implement an actual
.hashCode()
function, but I think we're too time-constrained here for that. The suggestion I've made there is to instead use the length of the stringified selection set as a hash function (as it's unaffected by order changes). Collisions will mostly happen at low sizes, but those should be fast to compare via.equals()
anyway, so it's fine in the short-term.- This means changing the map of stringified selection sets to named fragment definitions to instead become a map of hash codes (numbers) to lists of pairs, where each pair consists of a
SelectionSet
object, along with the named fragment definition. Lookup involves computing the hash of theSelectionSet
, and if an entry is found, iterating through the pairs in the list, comparing theSelectionSet
to the ones in the pairs via.equals()
. If a pair is equal, then theSelectionSet
is "in" the map, and the corresponding named fragment definition is the value. If no pair is equal (or no entry was found), then theSelectionSet
is not "in" the map, and we'd append to that list a new pair with thatSelectionSet
and the new named fragment definition (or if no entry was found, add an entry with the hash code and a new list containing just that pair).
- This means changing the map of stringified selection sets to named fragment definitions to instead become a map of hash codes (numbers) to lists of pairs, where each pair consists of a
I think "stringifying" selection set is going to be rather slow for computing the keys. Why not just go with something simple as If you are going to be pointing to an array of |
It's a tradeoff between hash function speed and collision rate. You can indeed use the top-level field info of the selection set for the hash function instead of the entire selection set (it's like using the prefix of a string for a string's hash function instead of the entire string). The cost is that you may encounter more collisions, which means more spurious In any case this is a short-term fix under a feature flag, so I'm fine with whatever hash function we choose here as long as the collision rate isn't too high. |
internals-js/src/operations.ts
Outdated
if (!equivalentSelectionSetCandidates) { | ||
seenSelections.set(mockHashCode, [[minimizedSelectionSet, fragmentDefinition]]); | ||
namedFragments.add(fragmentDefinition); | ||
} else { | ||
equivalentSelectionSetCandidates.push([minimizedSelectionSet, fragmentDefinition]); | ||
} |
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.
Since the comparison is against the pre-minified selection set, I think you'll want the pre-minified selection set in the pair instead of the minified selection set. (The NamedFragmentDefinition
should still use the minified selection set though.)
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.
Indeed there are way more complex hashing algos that we should look into in the future. Using the prefix just narrows down the selection sets to the same type condition and then we just check for number of selections there. I'd imagine instead doing the concatenating selection set into a string to get the final length we could simply recursively iterate over the selection sets and just aggregate the final length value (but unsure whether in practice it would be any better than recursively going through selection sets to calculate total number of selected fields). |
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.
One minor nit to fix in a followup
@@ -2413,7 +2478,7 @@ export function selectionOfElement(element: OperationElement, subSelection?: Sel | |||
return element.kind === 'Field' ? new FieldSelection(element, subSelection) : new InlineFragmentSelection(element, subSelection!); | |||
} | |||
|
|||
export type Selection = FieldSelection | FragmentSelection; | |||
export type Selection = FieldSelection | FragmentSelection | FragmentSpreadSelection; |
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.
FragmentSelection
is an abstract class implemented by InlineFragmentSelection
and FragmentSpreadSelection
, so I don't think you'll need to change the definition here.
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 noticed that |
Indeed when reusing fragments we apply a logic to ensure we have selection set > 1. For fragment generation logic we decided to keep it simple for now (we could definitely improve it in the future and provide some better optimizations, e.g. join all sibling inline fragments into one named fragment etc). |
…ts at least 2 selections - follow-up of apollographql#2958
…ts at least 2 selections (#2962) This is a follow-up of #2958. I've changed `SelectionSet.minimizeSelectionSet` to create a new fragment only when the selection set has at least 2 items. This behavior is inspired by the behavior of `reuseQueryFragments` option. `NamedFragments.selectionSetIsWorthUsing` was moved to `SelectionSet.isWorthUsing` and is now used by both `NamedFragments` and fragment generation logic.
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to main, this PR will be updated. # Releases ## @apollo/[email protected] ### Patch Changes - When a linked directive requires a federation version higher than the linked federation spec, upgrade to the implied version and issue a hint ([#2929](#2929)) - Stop emitting "inconsistent value type" hints against definitions where the type is marked `@external` or all fields are marked `@external`. ([#2951](#2951)) - Introduce a new composition hint pertaining specifically to progressive `@override` usage (when a `label` argument is present). ([#2922](#2922)) - Updated dependencies \[[`33b937b18d3c7ca6af14b904696b536399e597d1`](33b937b), [`09cd3e55e810ee513127b7440f5b11af7540c9b0`](09cd3e5), [`d7189a86c27891af408d3d0184db6133d3342967`](d7189a8)]: - @apollo/[email protected] - @apollo/[email protected] ## @apollo/[email protected] ### Patch Changes - Remove out-of-band reporting in the gateway and provide a warning for users who have the endpoint configured. ([#2946](#2946)) - Updated dependencies \[[`33b937b18d3c7ca6af14b904696b536399e597d1`](33b937b), [`09cd3e55e810ee513127b7440f5b11af7540c9b0`](09cd3e5), [`d7189a86c27891af408d3d0184db6133d3342967`](d7189a8), [`33506bef6d755c58400081824167711c1747ee40`](33506be), [`1f72f2a361a83ebaaf15ae052f5ca9a93fc18bfc`](1f72f2a)]: - @apollo/[email protected] - @apollo/[email protected] - @apollo/[email protected] ## @apollo/[email protected] ### Patch Changes - When a linked directive requires a federation version higher than the linked federation spec, upgrade to the implied version and issue a hint ([#2929](#2929)) - When auto-upgrading a subgraph (i.e. one that does not explicitly @link the federation spec) do not go past v2.4. This is so that subgraphs will not inadvertently require the latest join spec (which cause the router or gateway not to start if running an older version). ([#2933](#2933)) - Add new `generateQueryFragments` option to query planner config ([#2958](#2958)) If enabled, the query planner will extract inline fragments into fragment definitions before sending queries to subgraphs. This can significantly reduce the size of the query sent to subgraphs, but may increase the time it takes to plan the query. ## @apollo/[email protected] ### Patch Changes - Updated dependencies \[[`33b937b18d3c7ca6af14b904696b536399e597d1`](33b937b), [`09cd3e55e810ee513127b7440f5b11af7540c9b0`](09cd3e5), [`d7189a86c27891af408d3d0184db6133d3342967`](d7189a8)]: - @apollo/[email protected] ## @apollo/[email protected] ### Patch Changes - When auto-upgrading a subgraph (i.e. one that does not explicitly @link the federation spec) do not go past v2.4. This is so that subgraphs will not inadvertently require the latest join spec (which cause the router or gateway not to start if running an older version). ([#2933](#2933)) - Add new `generateQueryFragments` option to query planner config ([#2958](#2958)) If enabled, the query planner will extract inline fragments into fragment definitions before sending queries to subgraphs. This can significantly reduce the size of the query sent to subgraphs, but may increase the time it takes to plan the query. - Updated dependencies \[[`33b937b18d3c7ca6af14b904696b536399e597d1`](33b937b), [`09cd3e55e810ee513127b7440f5b11af7540c9b0`](09cd3e5), [`d7189a86c27891af408d3d0184db6133d3342967`](d7189a8)]: - @apollo/[email protected] - @apollo/[email protected] ## @apollo/[email protected] ### Patch Changes - Updated dependencies \[[`33b937b18d3c7ca6af14b904696b536399e597d1`](33b937b), [`09cd3e55e810ee513127b7440f5b11af7540c9b0`](09cd3e5), [`d7189a86c27891af408d3d0184db6133d3342967`](d7189a8)]: - @apollo/[email protected] ## [email protected] ### Patch Changes - Add new `generateQueryFragments` option to query planner config ([#2958](#2958)) If enabled, the query planner will extract inline fragments into fragment definitions before sending queries to subgraphs. This can significantly reduce the size of the query sent to subgraphs, but may increase the time it takes to plan the query. Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Ref: apollographql/federation#2958 Adopting this in router should take into consideration that both `reuse_query_fragments` and `generate_query_fragments` should not both be set to true. Since `reuse_` currently defaults to true, it should be disabled when `generate_` is enabled. --------- Co-authored-by: apollo-bot2 <[email protected]>
Closing the loop on apollographql/federation#2958 - [ ] I'm unsure if override + log warning is the appropriate action, do we have a similar pattern in config elsewhere? - [x] Is there a place where the yaml config options are all documented? I don't see anything existing for `reuse_query_fragments`. - [ ] How to test the log warning was emitted? --------- Co-authored-by: Bryn Cooke <[email protected]>
If enabled, the query planner will extract inline fragments into fragment definitions before sending queries to subgraphs. This can significantly reduce the size of the query sent to subgraphs, but may increase the time it takes to plan the query.
This is a reimplementation of #2893 that applies transformations on the selection set instead of AST.
Credit to @samuelAndalon for suggesting the new approach. Resolves #2892