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

Optimization: skip writing still-fresh result objects back into the EntityStore. #6274

Merged
merged 8 commits into from
May 13, 2020

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented May 13, 2020

If we're about to write a result object into the store, but we happen to know that the exact same (===) result object would be returned if we were to reread the result with the same inputs right now, then we can skip the rest of the processSelectionSet work for this object, and immediately return a Reference to it.

Note that this trick only works because result objects are immutable (frozen in development), which allows us to assume the structure of a === result object has not changed since it was originally returned.

The executeSelectionSet.peek method is also vital here, since we need a way to check the latest cached result without recomputing the result if it's missing or dirty. This PR depends on these two recent optimism PRs: benjamn/optimism#65, benjamn/optimism#66.

This optimization promises to make the readQuery-transform-writeQuery pattern much cheaper when the written data contain lots of unmodified result objects that we recently read from the cache, such as when you append a single Todo object to a long list of existing Todo objects.

Comment on lines +171 to +172
const latest = this.executeSelectionSet.peek(
store, selectionSet, parent, varString);
Copy link
Member Author

Choose a reason for hiding this comment

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

We don't actually have enough information here to call this.executeSelectionSet the normal way, but we can peek its value using the arguments returned by keyArgs, since that's enough to compute the cache key and look up the latest result.

@benjamn benjamn requested a review from hwillson May 13, 2020 16:40
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!

If we're about to write a result object into the store, but we happen to
know that the exact same (===) result object would be returned if we were
to re-read the result with the same inputs right now (store, selection
set, parent object, variables), then we can skip the rest of the
processSelectionSet work for this object.

Note that this trick only works because result objects are frozen (in
development), which allows us to assume the structure of the === result
object has not changed since it was originally returned.

The executeSelectionSet.peek method is also vital here, since we need a
way to check the latest cached result without recomputing the result if
it's missing or dirty.

This optimization makes the readQuery-transform-writeQuery pattern much
cheaper when the written data contains a bunch of unmodified result
objects that we recently read from the cache.
We were already doing this on the StoreReader side, but now we do it for
the StoreWriter context, too.
@benjamn benjamn force-pushed the skip-writing-fresh-cache-results branch from 53a2fd1 to 32b3f42 Compare May 13, 2020 18:56
@benjamn benjamn merged commit 635ca49 into master May 13, 2020
@benjamn benjamn deleted the skip-writing-fresh-cache-results branch May 13, 2020 20:35
benjamn added a commit that referenced this pull request May 16, 2020
The `cache.modify` API was first introduced in #5909 as a quick way to
transform the values of specific existing fields in the cache.

At the time, `cache.modify` seemed promising as an alternative to the
`readQuery`-transform-`writeQuery` pattern, but feedback has been mixed,
most importantly from our developer experience team, who helped me
understand why `cache.modify` would be difficult to learn and to teach.

While my refactoring in #6221 addressed concerns about broadcasting
`cache.modify` updates automatically, the bigger problem with
`cache.modify` is simply that it requires knowledge of the internal
workings of the cache, making it nearly impossible to explain to
developers who are not already Apollo Client 3 experts.

As much as I wanted `cache.modify` to be a selling point for Apollo Client
3, it simply wasn't safe to use without a firm understanding of concepts
like cache normalization, references, field identity, read/merge functions
and their options API, and the `options.toReference(object, true)` helper
function (#5970).

By contrast, the `readQuery`-transform-`writeQuery` pattern may be a bit
more verbose, but it has none of these problems, because these older
methods work in terms of GraphQL result objects, rather than exposing the
internal data format of the `EntityStore`.

Since `cache.modify` was motivated to some extent by vague power-user
performance concerns, it's worth noting that we recently made `writeQuery`
and `writeFragment` even more efficient when rewriting unchanged results
(#6274), so whatever performance gap there might have been between
`cache.modify` and `readQuery`/`writeQuery` should now be even less
noticeable.

One final reason that `cache.modify` seemed like a good idea was that
custom `merge` functions can interfere with certain `writeQuery` patterns,
such as deleting an item from a paginated list. If you run into trouble
with `merge` functions running when you don't want them to, we recommend
calling `cache.evict` before `cache.writeQuery` to ensure your `merge`
function won't be confused by existing field data when you write your
modified data back into the cache.
benjamn added a commit that referenced this pull request May 18, 2020
The `cache.modify` API was first introduced in #5909 as a quick way to
transform the values of specific existing fields in the cache.

At the time, `cache.modify` seemed promising as an alternative to the
`readQuery`-transform-`writeQuery` pattern, but feedback has been mixed,
most importantly from our developer experience team, who helped me
understand why `cache.modify` would be difficult to learn and to teach.

While my refactoring in #6221 addressed concerns about broadcasting
`cache.modify` updates automatically, the bigger problem with
`cache.modify` is simply that it requires knowledge of the internal
workings of the cache, making it nearly impossible to explain to
developers who are not already Apollo Client 3 experts.

As much as I wanted `cache.modify` to be a selling point for Apollo Client
3, it simply wasn't safe to use without a firm understanding of concepts
like cache normalization, references, field identity, read/merge functions
and their options API, and the `options.toReference(object, true)` helper
function (#5970).

By contrast, the `readQuery`-transform-`writeQuery` pattern may be a bit
more verbose, but it has none of these problems, because these older
methods work in terms of GraphQL result objects, rather than exposing the
internal data format of the `EntityStore`.

Since `cache.modify` was motivated to some extent by vague power-user
performance concerns, it's worth noting that we recently made `writeQuery`
and `writeFragment` even more efficient when rewriting unchanged results
(#6274), so whatever performance gap there might have been between
`cache.modify` and `readQuery`/`writeQuery` should now be even less
noticeable.

One final reason that `cache.modify` seemed like a good idea was that
custom `merge` functions can interfere with certain `writeQuery` patterns,
such as deleting an item from a paginated list. If you run into trouble
with `merge` functions running when you don't want them to, we recommend
calling `cache.evict` before `cache.writeQuery` to ensure your `merge`
function won't be confused by existing field data when you write your
modified data back into the cache.
benjamn added a commit that referenced this pull request May 18, 2020
The `cache.modify` API was first introduced in #5909 as a quick way to
transform the values of specific existing fields in the cache.

At the time, `cache.modify` seemed promising as an alternative to the
`readQuery`-transform-`writeQuery` pattern, but feedback has been mixed,
most importantly from our developer experience team, who helped me
understand why `cache.modify` would be difficult to learn and to teach.

While my refactoring in #6221 addressed concerns about broadcasting
`cache.modify` updates automatically, the bigger problem with
`cache.modify` is simply that it requires knowledge of the internal
workings of the cache, making it nearly impossible to explain to
developers who are not already Apollo Client 3 experts.

As much as I wanted `cache.modify` to be a selling point for Apollo Client
3, it simply wasn't safe to use without a firm understanding of concepts
like cache normalization, references, field identity, read/merge functions
and their options API, and the `options.toReference(object, true)` helper
function (#5970).

By contrast, the `readQuery`-transform-`writeQuery` pattern may be a bit
more verbose, but it has none of these problems, because these older
methods work in terms of GraphQL result objects, rather than exposing the
internal data format of the `EntityStore`.

Since cache.modify was motivated to some extent by vague power-user
performance concerns, it's worth noting that we recently made writeQuery
and writeFragment even more efficient when rewriting unchanged results (#6274),
so whatever performance gap there might have been between cache.modify
and readQuery/writeQuery should now be even less noticeable.

One final reason that `cache.modify` seemed like a good idea was that
custom `merge` functions can interfere with certain `writeQuery` patterns,
such as deleting an item from a paginated list. If you run into trouble
with `merge` functions running when you don't want them to, we recommend
calling `cache.evict` before `cache.writeQuery` to ensure your `merge`
function won't be confused by existing field data when you write your
modified data back into the cache.
@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