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

Support eviction of specific entity fields. #5643

Merged
merged 4 commits into from
Dec 3, 2019

Conversation

benjamn
Copy link
Member

@benjamn benjamn commented Dec 2, 2019

This PR adds an additional (optional) parameter to the cache.evict method, to delete a specific field from a normalized entity by calling cache.evict(id, fieldName). The shorter cache.evict(id) version works as before.

Please read the commit messages to understand what was tricky/subtle about implementing this interface. In particular, fieldName is the actual name of the field, as defined in your schema, and thus it may refer to multiple field values (if the field takes arguments), all of which should be invalidated when the field is deleted.

@benjamn benjamn added this to the Release 3.0 milestone Dec 2, 2019
@benjamn benjamn self-assigned this Dec 2, 2019
@@ -206,12 +206,12 @@ export class InMemoryCache extends ApolloCache<NormalizedCacheObject> {
return this.policies.identify(object, selectionSet, fragmentMap);
}

public evict(dataId: string): boolean {
public evict(dataId: string, fieldName?: string): 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.

It was tempting to add cache.evictRootQueryField(fieldName) as a shorthand for cache.evict("ROOT_QUERY", fieldName), but then I realized it was the same number of characters, which is a bad news for a shorthand.

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.

Looks great @benjamn!

When a field has multiple cached values due to being written into the
cache at different times with different arguments, it's important that we
do not throw away the association between the short field.name.value (an
identifier, as defined in the schema) and the longer storeFieldName
strings computed from the field and its arguments.

This commit ensures that field.name.value is always recoverable from the
storeFieldName, by prepending field.name.value + ":" to any storeFieldName
that does not already start with field.name.value.
In PR #5617, we shifted from using just IDs for caching results read from
the EntityStore to using IDs plus field names, which made the result
caching system significantly more precise and less likely to trigger
unnecessary rerenders.

However, the field names we used in that PR were storeFieldName strings,
instead of the actual field names you'd find in your GraphQL schema.
Within the InMemoryCache implementation, a storeFieldName is the full
string produced by Policies#getStoreFieldName, which must begin with the
original field.name.value, but may include an additional suffix that
captures field identity based on arguments (and also, but less frequently,
directives).

For the purposes of result caching, it's important to depend on the entity
ID plus the actual field name (field.name.value), rather than the ID plus
the storeFieldName, because there can be many different storeFieldName
strings for a single field.name.value (and we don't want to waste memory
on dependency tracking), and because it's safer (more conservative) to
dirty all fields with a particular field.name.value when any field with a
matching storeFieldName is updated or evicted, since field values with the
same field.name.value often overlap in subtle ways (for example, multiple
pages of a paginated list), so it seems dangerous to encourage deleting
some but not all of them.

Perhaps more importantly, application developers should never have to
worry about the concept of a storeFieldName, since it is very much an
implementation detail of the InMemoryCache, and the precise format of
storeFieldName strings is subject to change.
Evicting an entire entity object is often overkill, when all you really
want is to invalidate and refetch specific fields within the entity. This
observation is especially true for the ROOT_QUERY object, which should
never be evicted in its entirety, but whose individual fields often become
stale or need to be recomputed.

A critical nuance here is that fields are evicted according to their
field.name.value, rather than a specific storeFieldName, since it doesn't
make a whole lot of sense to evict the field value associated with a
specific set of arguments. Instead, calling cache.evict(id, fieldName)
will evict *all* values for that field, regardless of the arguments.
@benjamn benjamn force-pushed the allow-evicting-specific-fields branch from 9c33678 to 5f153e8 Compare December 3, 2019 20:10
@benjamn benjamn changed the base branch from cache.identify to release-3.0 December 3, 2019 20:11
@benjamn benjamn merged commit b709e86 into release-3.0 Dec 3, 2019
@benjamn benjamn mentioned this pull request Dec 3, 2019
31 tasks
@benjamn benjamn deleted the allow-evicting-specific-fields branch December 3, 2019 20:24
@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.

None yet

2 participants