-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
Fully eliminate the concept of heuristic fragment matching. #5684
Conversation
Although we removed the FragmentMatcher abstraction in PR #5073, and the HeuristicFragmentMatcher no longer exists, we kept trying to match fragments whenever all their fields matched, even if the fragment type condition was not satisfied by the __typename of the object in question. I recently came to appreciate just how harmful this best-effort matching can be, when I realized that it means we call executeSelectionSet (on the reading side) and processSelectionSet (on the writing side) for every unmatched fragment, and then just throw away the result if any fields are missing, but not before recording those fields as missing in a weaker way than usual, using the ExecResultMissingField.tolerable boolean. Not only is this strategy potentially costly for queries with lots of fragments that really should not match, it also only ever made sense on the writing side, where the fields of the fragment can be compared to the fields of an incoming result object, so the presence of all the fragment's fields is a pretty good indication that the fragment matched. On the reading side, we're comparing the fields of the fragment to all the fields we've written into the cache so far, which means heuristic fragment matching is sensitive to the whole history of cache writes for the entity in question, not just the current query. We could eliminate heuristic fragment matching just for reading, but then we'd have to tell people to pass possibleTypes to the InMemoryCache constructor (the correct, non-heuristic solution), which would make heuristic fragment matching unnecessary on the writing side, too, so we might as well completely eliminate heuristic matching altogether.
When we're writing to an entity ID that exists in the store, and the normalized entity object has a __typename field (as almost all of them should), we can use that __typename as a fallback when the result object that we're writing to the store is missing a __typename field.
Errors due to missing fields from heuristically-matched fragments were previously suppressed because there was a chance the heuristic was wrong. Since we're no longer doing any unsound heuristic fragment matching, there's no longer such a thing as a tolerable missing field. This change should make fixing issue #5614 unnecessary.
GitHub appears to be having some serious problems updating in response to |
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.
Woohoo! (now excuse me while I close 30% of our open issues 😂).
@@ -377,10 +377,18 @@ export class Policies { | |||
public fragmentMatches( | |||
fragment: InlineFragmentNode | FragmentDefinitionNode, | |||
typename: string, | |||
): boolean | "heuristic" { | |||
): boolean { |
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.
Super happy to see this just return a boolean
.
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.
Words like "truthy" and "heuristic" belong in the same circle of word-hell.
Although we removed the
FragmentMatcher
abstraction in PR #5073, and theHeuristicFragmentMatcher
no longer exists, we kept trying to match fragments whenever all their fields matched, even if the fragment type condition was not satisfied by the__typename
of the object in question.I recently came to appreciate just how harmful this best-effort matching can be, when I realized that it means we call
executeSelectionSet
(on the reading side) andprocessSelectionSet
(on the writing side) for every unmatched fragment, and then just throw away the result if any fields are missing, but not before recording those fields as missing in a weaker way than usual, using theExecResultMissingField.tolerable
boolean.Not only is this strategy potentially costly for queries with lots of fragments that really should not match, it also only ever made sense on the writing side, where the fields of the fragment can be compared to the fields of a single incoming result object, so the presence of all the fragment's fields is a pretty good indication that the fragment matched. On the reading side, we're comparing the fields of the fragment to all the fields we've written into the cache so far, which means heuristic fragment matching is sensitive to the whole history of cache writes for the entity in question, not just the current query.
We could eliminate heuristic fragment matching just for reading, but then we'd have to tell people to start passing
possibleTypes
to theInMemoryCache
constructor (the correct, non-heuristic solution), which would simultaneously make heuristic fragment matching unnecessary on the writing side, so we might as well completely eliminate heuristic matching altogether.