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

Stop feuds by skipping cache writes for unchanged network results. #6448

Merged
merged 3 commits into from
Jun 16, 2020

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Jun 16, 2020

Ever since the big refactoring in #6221 made the client more aggressive about triggering network requests in response to incomplete cache data (when using a FetchPolicy of cache-first), folks testing the betas/RCs have reported that feuding queries can end up triggering an endless loop of unhelpful network requests.

This change does not solve the more general problem of queries competing with each other to update the same data in incompatible ways (see #6372 for other mitigation strategies), but I believe this commit will effectively put a stop to most cases of endless network requests.

See my lengthy implementation comment for more details, since this is a non-obvious solution to a very tricky problem.

Should fix #6307, #6370, #6434, and #6444.

Ever since the big refactoring in #6221 made the client more aggressive
about triggering network requests in response to incomplete cache data
(when using a cache-first FetchPolicy), folks testing the betas/RCs have
reported that feuding queries can end up triggering an endless loop of
unhelpful network requests.

This change does not solve the more general problem of queries competing
with each other to update the same data in incompatible ways (see #6372
for mitigation strategies), but I believe this commit will effectively put
a stop to most cases of endless network requests.

See my lengthy implementation comment for more details, since this is a
non-obvious solution to a very tricky problem.

Fixes #6307.
Fixes #6370.
Fixes #6434.
Fixes #6444.
Comment on lines +226 to +259
if (equal(result, this.lastWrittenResult) &&
equal(options.variables, this.lastWrittenVars)) {
// If result is the same as the last result we received from
// the network (and the variables match too), avoid writing
// result into the cache again. The wisdom of skipping this
// cache write is far from obvious, since any cache write
// could be the one that puts the cache back into a desired
// state, fixing corruption or missing data. However, if we
// always write every network result into the cache, we enable
// feuds between queries competing to update the same data in
// incompatible ways, which can lead to an endless cycle of
// cache broadcasts and useless network requests. As with any
// feud, eventually one side must step back from the brink,
// letting the other side(s) have the last word(s). There may
// be other points where we could break this cycle, such as
// silencing the broadcast for cache.writeQuery (not a good
// idea, since it just delays the feud a bit) or somehow
// avoiding the network request that just happened (also bad,
// because the server could return useful new data). All
// options considered, skipping this cache write seems to be
// the least damaging place to break the cycle, because it
// reflects the intuition that we recently wrote this exact
// result into the cache, so the cache *should* already/still
// contain this data. If some other query has clobbered that
// data in the meantime, that's too bad, but there will be no
// winners if every query blindly reverts to its own version
// of the data. This approach also gives the network a chance
// to return new data, which will be written into the cache as
// usual, notifying only those queries that are directly
// affected by the cache updates, as usual. In the future, an
// even more sophisticated cache could perhaps prevent or
// mitigate the clobbering somehow, but that would make this
// particular cache write even less important, and thus
// skipping it would be even safer than it is today.
Copy link
Member Author

Choose a reason for hiding this comment

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

Just surfacing this implementation comment, since it explains the design space as I understand it.

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.

This looks awesome @benjamn - 🤞this will address most of the infinite loop cases. Thanks for providing such a detailed explanation as well 👍.

@benjamn benjamn merged commit 0962d25 into master Jun 16, 2020
@benjamn benjamn deleted the skip-cache-writes-for-unchanged-network-results branch June 16, 2020 20:48
benjamn added a commit that referenced this pull request Aug 10, 2020
When application code evicts an object or its fields from the cache, and
those evictions trigger network requests, the network data is often vital
for repairing the damage done by the eviction, even if the network data
look exactly the same as the previously-written data.

In other words, after any cache eviction, we should disable the logic
introduced in PR #6448 to stop query feuds. This exception is reasonable
because feuding queries only write additional data into the cache, rather
than evicting anything, so we can safely assume evictions do not
contribute to a cycle of competing cache updates.

I freely admit the method of counting evictions that I've implemented here
is a bit of a hack, but it works for any ApolloCache implementation,
without requiring the public ApolloCache API to support any new
functionality. If we identify any other potential consumers of this
information, we might consider a more official API.
benjamn added a commit that referenced this pull request Aug 10, 2020
When application code evicts an object or its fields from the cache, and
those evictions trigger network requests, the network data is often vital
for repairing the damage done by the eviction, even if the network data
look exactly the same as the previously-written data.

In other words, after any cache eviction, we should disable the logic
introduced in PR #6448 to stop query feuds. This exception is reasonable
because feuding queries only write additional data into the cache, rather
than evicting anything, so we can safely assume evictions do not
contribute to a cycle of competing cache updates.

I freely admit the method of counting evictions that I've implemented here
is a bit of a hack, but it works for any ApolloCache implementation,
without requiring the public ApolloCache API to support any new
functionality. If we identify any other potential consumers of this
information, we might consider a more official API.
hwillson pushed a commit that referenced this pull request Aug 21, 2020
When application code evicts an object or its fields from the cache, and
those evictions trigger network requests, the network data is often vital
for repairing the damage done by the eviction, even if the network data
look exactly the same as the previously-written data.

In other words, after any cache eviction, we should disable the logic
introduced in PR #6448 to stop query feuds. This exception is reasonable
because feuding queries only write additional data into the cache, rather
than evicting anything, so we can safely assume evictions do not
contribute to a cycle of competing cache updates.

I freely admit the method of counting evictions that I've implemented here
is a bit of a hack, but it works for any ApolloCache implementation,
without requiring the public ApolloCache API to support any new
functionality. If we identify any other potential consumers of this
information, we might consider a more official API.
@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.

Fetching data again and again if more queries without id executed
2 participants