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

Fully eliminate the concept of heuristic fragment matching. #5684

Merged
merged 6 commits into from
Dec 13, 2019

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Dec 13, 2019

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 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 the InMemoryCache 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.

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.
@benjamn
Copy link
Member Author

benjamn commented Dec 13, 2019

GitHub appears to be having some serious problems updating in response to git push right now, FYI: https://www.githubstatus.com/incidents/4g79klqzy99s

@benjamn benjamn mentioned this pull request Dec 13, 2019
31 tasks
Copy link
Member

@hwillson hwillson left a 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 {
Copy link
Member

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.

Copy link
Member Author

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.

@benjamn benjamn merged commit 51ef271 into release-3.0 Dec 13, 2019
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Feb 16, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants