-
Notifications
You must be signed in to change notification settings - Fork 248
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
Overfetching on union
types with conditional fragments in query operation
#2759
Comments
union
types with conditional fragments in query operationunion
types with conditional fragments in query operation
Thanks for raising this request! Our team has added the item to our backlog, but it does require a bit more design and thought before we can prioritize it. We will definitely keep this issue updated once it's been officially prioritized, and we are also open to community contributions. |
from our stand point, this issue would deserve a better priority that P4 as it affects one fundamental aspect of graphql (prevent overfetching). The resolution of this issue by Apollo is very important to us at Wayfair. |
We're seeing a latency impact from the query planner "popping" fields from union fragments (which should be conditional on the type being present in the actual result set) up into a sequential fetch and over fetching. The reproduction looks something like this: # Subgraph 1
type Query {
card: ProductCard
}
type ProductCard @key(fields: "id") {
id: ID!
components: Component
}
type Product @key(fields: "newKey") {
newKey: ID!
}
union Component = ComponentA | ComponentB | ComponentC
type ComponentA {
a: String
product: Product
}
type ComponentB {
b: String
product: Product
}
type ComponentC {
c: String
product: Product
} # Subgraph 2
type Product @key(fields: "oldKey") @key(fields: "newKey") {
oldKey: ID!
newKey: ID!
a: String!
} # Subgraph 3
type Product @key(fields: "oldKey") {
oldKey: ID!
b: String!
} # Subgraph 4
type Product @key(fields: "oldKey") {
oldKey: ID!
c: String!
} # Query
query Foo {
card {
components {
... on ComponentA {
__typename
product {
__typename
a
}
}
... on ComponentB {
__typename
product {
__typename
b
}
}
... on ComponentC {
__typename
product {
__typename
c
}
}
}
}
} Expected behaviorThe expectation is that Router would:
Here's the expected query plan (or something similar to this is expected):
Actual behaviorInstead, Router does the following:
Here's the query plan:
Current Band-aid fixesI have identified two band-aid fixes for this example. But, to be clear, the do not fix the problem. These are just two ways to try and force the query plan shape to change. Option 1If the Option 2If field SummaryThe crux of the issue is that router is overly eager to reduce subgraph requests at the cost of overfetching. This breaks a core tenant of GraphQL which is only getting what you ask for. The returned shape of the union does not dictate that field Ultimately, the Query planner needs to consider fragments on union members as unique units of the operation and should not pop fields out of those fragments. In some cases, yes it will result in additional requests that in some cases could have been avoided. But it's that or over fetching and, IMO, over fetching is the worse of two evils. This is until the query planner is able to include if/else conditions in it's static flow (which is another conversation for another day). This is why I support elevating this higher than a P4 issue. This really does feel more like a P1 or P2 at the lowest because it will result in UI, UX, and very difficult to determine, quantify, and communicate schema design constraints. I'm already having conversations with teams about what features they need to cut, or what subgraphs need to be broken apart to try and influence the query plan to avoid this overfetching. That's not a concern that should be leaking into the domain separation. |
FWIW, I haven't confirmed this but I imagine the problem could surface for interface fragments as well. That should probably also be considered when solving this problem. |
…s that I need to go back and review to see if any are real problems. Also, I think that the initial fetch of `oldKey` in the added test may need to include __typename Refs #2759
The |
👋 looks like we forgot to close this issue with the #2949 PR. This should be available since fed v2.7.3 and supported by router version 1.46+. Thanks for the reminder! |
Issue Description
When including conditional fragments on a union field the resolvers for fragments appear to be requested unconditionally from the router.
In the example below, the resolver for
Product#c
is invoked when the conditional fragment for... on ComponentC
is not needed given a response without any instances ofComponentC
from thecard
service. Removing the conditional fragment for... on ComponentC
prevents theProduct#c
value from being requested, as expected.Our production
router
is currently usingv1.20.0
but we also confirmed that this issue exists onv1.28.0
. This also impacts thegateway
as seen in the reproduction link.Subgraph A Schema(Card)
Subgraph B Schema (Product)
Query
Query Plan
Response
Link to Reproduction
https://codesandbox.io/p/sandbox/eloquent-christian-kcs989
Reproduction Steps
Run the query above, and notice the
C resolver -- BAD
andB resolver -- BAD
in the terminal output.The text was updated successfully, but these errors were encountered: