Docs
Launch GraphOS Studio
Since 3.9.0

Memory management

Learn how to choose and set custom cache sizes


Cache Sizes

For better performance, caches (or, in other words, memoizes) many internally calculated values. In most cases, these values are cached in weak caches, which means that if the source object is garbage-collected, the cached value will be garbage-collected, too.

These caches are also Least Recently Used (LRU) caches, meaning that if the cache is full, the least recently used value will be garbage-collected.

Depending on your application, you might want to tweak the cache size to fit your needs.

You can set your cache size before (recommended) or after loading the Apollo Client library.

Setting cache sizes before loading the Apollo Client library

Setting cache sizes before loading the Apollo Client library is recommended because some caches are already initialized when the library is loaded. Changed cache sizes only affect caches created after the fact, so you'd have to write additional runtime code to recreate these caches after changing their size.

import type { CacheSizes } from '@apollo/client/utilities';
globalThis[Symbol.for("apollo.cacheSize")] = {
parser: 100,
"fragmentRegistry.lookup": 500
} satisfies Partial<CacheSizes>

Adjusting cache sizes after loading the Apollo Client library

You can also adjust cache sizes after loading the library.

import { cacheSizes } from '@apollo/client/utilities';
import { print } from '@apollo/client'
cacheSizes.print = 100;
// cache sizes changed this way will only take effect for caches
// created after the cache size has been changed, so we need to
// reset the cache for it to be effective
print.reset();

Choosing appropriate cache sizes

All configurable caches hold memoized values. If an item is cache-collected, it incurs only a small performance impact and doesn't cause data loss. A smaller cache size might save you memory.

You should choose cache sizes appropriate for storing a reasonable number of values rather than every value. To prevent too much recalculation, choose cache sizes that are at least large enough to hold memoized values for all hooks/queries on the screen at any given time.

To choose good sizes for our memoization caches, you need to know what they use as source values, and have a general understanding of the data flow inside of Apollo Client.

For most memoized values, the source value is a parsed a DocumentNode. There are two types:

  • User-supplied DocumentNodes are created by the user, for example by using the gql template literal tag. This is the QUERY, MUTATION, or SUBSCRIPTION passed into a useQuery hook or as the query option to client.query.
  • Transformed DocumentNodes are derived from user-supplied DocumentNodes, for example, by applying DocumentTransforms to them.

As a rule of thumb, you should set the cache sizes for caches using a transformed DocumentNode at least to the same size as for caches using a user-supplied DocumentNode. If your application uses a custom DocumentTransform that does not always transform the same input to the same output, you should set the cache size for caches using a Transformed DocumentNode to a higher value than for caches using a user-supplied DocumentNode.

By default, Apollo Client uses a base value of 1000 cached objects for caches using user-supplied DocumentNode instances, and scales other cache sizes relative to that. For example, the default base value of 1000 for user-provided DocumentNodes would scale to 2000, 4000, etc. for transformed DocumentNodes, depending on the transformation performed.

This base value should be plenty for most applications, but you can tweak them if you have different requirements.

Measuring cache usage

Since estimating appropriate cache sizes for your application can be hard, Apollo Client exposes an API for cache usage measurement.
This way, you can click around in your application and then take a look at the actual usage of the memoizing caches.

Keep in mind that this API is primarily meant for usage with the Apollo DevTools (an integration is coming soon), and the API may change at any point in time.
It is also only included in development builds, not in production builds.

NOTE

The cache usage API is only meant for manual measurements. Don't rely on it in production code or tests.

console.log(client.getMemoryInternals())

Logs output in the following JSON format:

Read more...
{
limits: {
parser: 1000,
canonicalStringify: 1000,
print: 2000,
'documentTransform.cache': 2000,
'queryManager.getDocumentInfo': 2000,
'PersistedQueryLink.persistedQueryHashes': 2000,
'fragmentRegistry.transform': 2000,
'fragmentRegistry.lookup': 1000,
'fragmentRegistry.findFragmentSpreads': 4000,
'cache.fragmentQueryDocuments': 1000,
'removeTypenameFromVariables.getVariableDefinitions': 2000,
'inMemoryCache.maybeBroadcastWatch': 5000,
'inMemoryCache.executeSelectionSet': 10000,
'inMemoryCache.executeSubSelectedArray': 5000
},
sizes: {
parser: 26,
canonicalStringify: 4,
print: 14,
addTypenameDocumentTransform: [
{
cache: 14,
},
],
queryManager: {
getDocumentInfo: 14,
documentTransforms: [
{
cache: 14,
},
{
cache: 14,
},
],
},
fragmentRegistry: {
findFragmentSpreads: 34,
lookup: 20,
transform: 14,
},
cache: {
fragmentQueryDocuments: 22,
},
inMemoryCache: {
executeSelectionSet: 4345,
executeSubSelectedArray: 1206,
maybeBroadcastWatch: 32,
},
links: [
{
PersistedQueryLink: {
persistedQueryHashes: 14,
},
},
{
removeTypenameFromVariables: {
getVariableDefinitions: 14,
},
},
],
},
}

Cache options

Other

"cache.fragmentQueryDocuments"

number

Cache size for the getFragmentDoc method of ApolloCache.

This function is called with user-provided definitions.

This function is called from readFragment with user-provided fragment definitions.

Cache size for the cache of DocumentTransform instances with the cache option set to true.

Can be called with user-defined or already-transformed DocumentNodes.

The cache size here should be chosen with other DocumentTransforms in mind. For example, if there was a DocumentTransform that would take x DocumentNodes, and returned a differently-transformed DocumentNode depending if the app is online or offline, then we assume that the cache returns 2*x documents. If that were concatenated with another DocumentTransform that would also duplicate the cache size, you'd need to account for 4*x documents returned by the second transform.

Due to an implementation detail of Apollo Client, if you use custom document transforms you should always add n (the "base" number of user-provided Documents) to the resulting cache size.

If we assume that the user-provided transforms receive n documents and return n documents, the cache size should be 2*n.

If we assume that the chain of user-provided transforms receive n documents and return 4*n documents, the cache size should be 5*n.

This size should also then be used in every other cache that mentions that it operates on a "transformed" DocumentNode.

Cache size for the findFragmentSpreads method of FragmentRegistry.

This function is called with transformed DocumentNodes, as well as recursively with every fragment spread referenced within that, or a fragment referenced by a fragment spread.

Note: This function is a dependency of fragmentRegistry.transform, so having too small of cache size here might involuntarily invalidate values in the transform cache.

A cache inside of FragmentRegistry.

This function is called with fragment names in the form of a string.

The size of this case should be chosen with the number of in your application in mind.

Note: This function is a dependency of fragmentRegistry.transform, so having too small of a cache size here might involuntarily invalidate values in the transform cache.

A cache inside of FragmentRegistry.

Can be called with user-defined or already-transformed DocumentNodes.

Cache size for the executeSelectionSet method on StoreReader.

Note: executeSelectionSet will be set to the resultCacheMaxSize option and will fall back to this configuration value if the option is not set.

Every object that is read from the cache will be cached here, so it is recommended to set this to a high value.

Cache size for the executeSubSelectedArray method on StoreReader.

Note: executeSubSelectedArray will be set to the resultCacheMaxSize option and will fall back to this configuration value if the option is not set.

Every array that is read from the cache will be cached here, so it is recommended to set this to a high value.

Cache size for the maybeBroadcastWatch method on InMemoryCache.

Note: maybeBroadcastWatch will be set to the resultCacheMaxSize option and will fall back to this configuration value if the option is not set.

This method is used for dependency tracking in the InMemoryCache and prevents from unnecessary re-renders. It is recommended to keep this value significantly higher than the number of possible subscribers you will have active at the same time in your application at any time.

A cache inside of PersistedQueryLink.

It is called with transformed DocumentNodes.

This cache is used to cache the hashes of .

A cache inside of QueryManager.

It is called with transformed DocumentNodes.

Cache used in removeTypenameFromVariables.

This function is called transformed DocumentNodes.

Cache used by canonicalStringify.

This cache contains the sorted keys of objects that are stringified by canonicalStringify. It uses the stringified unsorted keys of objects as keys. The cache will not grow beyond the size of different object shapes encountered in an application, no matter how much actual data gets stringified.

number

Cache size for the parser function.

It is called with user-provided DocumentNodes.

This method is called by HOCs and hooks.

number

Cache size for the print function.

It is called with transformed DocumentNodes.

This method is called to transform a GraphQL AST parsed by gql back into a GraphQL string.

Previous
Customizing field behavior
Next
Advanced topics
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company