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 the ApolloClient object.

cache.gc()

The gc method removes all objects from the normalized cache that are not reachable:

JavaScript
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:

JavaScript
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:

JavaScript
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:

JavaScript
1cache.retain('my-object-id');

If you later want a retained object to be garbage collected, use the release method:

JavaScript
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:

JavaScript
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:

JavaScript
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 evicting one or more objects from the cache.

Dangling references

When an object is evicted 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:

JavaScript
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 for Query.ruler returns a default ruler (Apollo) if the existingRuler has been deposed.

  • The read function for Deity.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.

Feedback

Edit on GitHub

Forums