Garbage collection and cache eviction
Apollo Client 3 enables you to selectively remove cached data that is no longer useful. The default garbage collection strategy of the gc
method is suitable for most applications, but the evict
method provides more fine-grained control for applications that require it.
You call these methods directly on the
InMemoryCache
object, not on theApolloClient
object.
cache.gc()
The gc
method removes all objects from the normalized cache that are not reachable:
1cache.gc();
To determine whether an object is reachable, the cache starts from all known root objects (usually Query
and/or Mutation
) and uses a tracing strategy to recursively visit all available child references. Any normalized objects that are not visited during this process are removed. The cache.gc()
method returns a list of the IDs of the removed objects.
In addition to pruning your GraphQL data, cache.gc
can also release memory that the cache uses to preserve unchanged parts of previous cache results:
1cache.gc({ resetResultCache: true })
Freeing this memory temporarily slows down cache reads, because those reads don't benefit from any previous reading work.
If you're using the canonizeResults: true
option, cache.gc
can also reset the memory used for looking up canonical result objects:
1cache.gc({
2 resetResultCache: true,
3 resetResultIdentities: true,
4})
**⚠️ Deprecation warning for
canonizeResults**: Using
canonizeResults` can result in memory leaks so we generally do not recommend using this option anymore. A future version of Apollo Client will contain a similar feature without the risk of memory leaks.
These additional cache.gc
options can be useful for investigating memory usage patterns or leaks. Before taking heap snapshots or recording allocation timelines, it's a good idea to force JavaScript garbage collection using your browser's devtools, to ensure memory released by the cache has been fully collected and returned to the heap.
Configuring garbage collection
You can use the retain
method to prevent an object (and its children) from being garbage collected, even if the object isn't reachable:
1cache.retain('my-object-id');
If you later want a retain
ed object to be garbage collected, use the release
method:
1cache.release('my-object-id');
If the object is unreachable, it will be garbage collected during next call to gc
.
cache.evict()
You can remove any normalized object from the cache using the evict
method:
1cache.evict({ id: 'my-object-id' })
You can also remove a single field from a cached object by providing the name of the field to remove:
1cache.evict({ id: 'my-object-id', fieldName: 'yearOfFounding' });
Evicting an object often makes other cached objects unreachable. Because of this, you should call cache.gc
after evict
ing one or more objects from the cache.
Dangling references
When an object is evict
ed from the cache, references to that object might remain in other cached objects. Apollo Client preserves these dangling references by default, because the referenced object might be written back to the cache at a later time. This means the reference might still be useful.
You can customize behavior for dangling references by defining a custom read
function for any field that might contain one. This function can perform whatever cleanup is necessary when the field's referenced object is missing. For example, the read
function might:
Filter the referenced object out of a list of available objects
Set the field's value to
null
Return a particular default value
Every read
function is passed a canRead
function that helps it detect when its field currently contains a dangling reference.
The following code defines two read
functions (one for Query.ruler
and one for Deity.offspring
) that both use canRead
:
1new InMemoryCache({
2 typePolicies: {
3 Query: {
4 fields: {
5 ruler(existingRuler, { canRead, toReference }) {
6 // If there is no existing ruler, Apollo becomes the ruling deity
7 return canRead(existingRuler) ? existingRuler : toReference({
8 __typename: "Deity",
9 name: "Apollo",
10 });
11 },
12 },
13 },
14
15 Deity: {
16 keyFields: ["name"],
17 fields: {
18 offspring(existingOffspring: Reference[], { canRead }) {
19 // Filter out any dangling references left over from removing
20 // offspring, supplying a default empty array if there are no
21 // offspring left.
22 return existingOffspring
23 ? existingOffspring.filter(canRead)
24 : [];
25 },
26 },
27 },
28 },
29})
The
read
function forQuery.ruler
returns a default ruler (Apollo
) if theexistingRuler
has been deposed.The
read
function forDeity.offspring
filters its array to return only offspring that are alive and well in the cache.
Filtering dangling references out of a cached list field (like the Deity.offspring
example above) is so common that the default read
function for a list field performs this filtering automatically. You can define a custom read
function to override this behavior.
There isn't a similarly common solution for a field that contains a single dangling reference (like the Query.ruler
example above), so this is where writing a custom read
function comes in handy most often.