Memory management
Learn how to choose and set custom cache sizes
Cache Sizes
For better performance, Apollo Client 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.
1import type { CacheSizes } from '@apollo/client/utilities';
2
3globalThis[Symbol.for("apollo.cacheSize")] = {
4 parser: 100,
5 "fragmentRegistry.lookup": 500
6} satisfies Partial<CacheSizes>
Adjusting cache sizes after loading the Apollo Client library
You can also adjust cache sizes after loading the library.
1import { cacheSizes } from '@apollo/client/utilities';
2import { print } from '@apollo/client'
3
4cacheSizes.print = 100;
5// cache sizes changed this way will only take effect for caches
6// created after the cache size has been changed, so we need to
7// reset the cache for it to be effective
8
9print.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 GraphQL document—
a DocumentNode
. There are two types:
User-supplied
DocumentNode
s are created by the user, for example by using thegql
template literal tag. This is theQUERY
,MUTATION
, orSUBSCRIPTION
argument passed into auseQuery
hook or as thequery
option toclient.query
.Transformed
DocumentNode
s are derived from user-suppliedDocumentNode
s, for example, by applyingDocumentTransform
s 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 DocumentNode
s would scale to 2000, 4000, etc. for transformed DocumentNode
s, 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.
1console.log(client.getMemoryInternals())
2
Logs output in the following JSON format:
Read more...
1{
2 limits: {
3 parser: 1000,
4 canonicalStringify: 1000,
5 print: 2000,
6 'documentTransform.cache': 2000,
7 'queryManager.getDocumentInfo': 2000,
8 'PersistedQueryLink.persistedQueryHashes': 2000,
9 'fragmentRegistry.transform': 2000,
10 'fragmentRegistry.lookup': 1000,
11 'fragmentRegistry.findFragmentSpreads': 4000,
12 'cache.fragmentQueryDocuments': 1000,
13 'removeTypenameFromVariables.getVariableDefinitions': 2000,
14 'inMemoryCache.maybeBroadcastWatch': 5000,
15 'inMemoryCache.executeSelectionSet': 10000,
16 'inMemoryCache.executeSubSelectedArray': 5000
17 },
18 sizes: {
19 parser: 26,
20 canonicalStringify: 4,
21 print: 14,
22 addTypenameDocumentTransform: [
23 {
24 cache: 14,
25 },
26 ],
27 queryManager: {
28 getDocumentInfo: 14,
29 documentTransforms: [
30 {
31 cache: 14,
32 },
33 {
34 cache: 14,
35 },
36 ],
37 },
38 fragmentRegistry: {
39 findFragmentSpreads: 34,
40 lookup: 20,
41 transform: 14,
42 },
43 cache: {
44 fragmentQueryDocuments: 22,
45 },
46 inMemoryCache: {
47 executeSelectionSet: 4345,
48 executeSubSelectedArray: 1206,
49 maybeBroadcastWatch: 32,
50 },
51 links: [
52 {
53 PersistedQueryLink: {
54 persistedQueryHashes: 14,
55 },
56 },
57 {
58 removeTypenameFromVariables: {
59 getVariableDefinitions: 14,
60 },
61 },
62 ],
63 },
64}
65
Cache options
Cache size for the getFragmentDoc
method of ApolloCache
.
This function is called with user-provided fragment definitions.
Read more...
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 DocumentNode
s.
Read more...
The cache size here should be chosen with other DocumentTransform
s in mind. For example, if there was a DocumentTransform
that would take x
DocumentNode
s, 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 DocumentNode
s, as well as recursively with every fragment spread referenced within that, or a fragment referenced by a fragment spread.
Read more...
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.
Read more...
The size of this case should be chosen with the number of fragments 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 DocumentNode
s.
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.
Read more...
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.
Read more...
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.
Read more...
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 DocumentNode
s.
Read more...
This cache is used to cache the hashes of persisted queries.
A cache inside of QueryManager
.
It is called with transformed DocumentNode
s.
Cache used in removeTypenameFromVariables
.
This function is called transformed DocumentNode
s.
number
Cache used by canonicalStringify
.
Read more...
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 DocumentNode
s.
Read more...
This method is called by HOCs and hooks.
number
Cache size for the print
function.
It is called with transformed DocumentNode
s.
Read more...
This method is called to transform a GraphQL query AST parsed by gql
back into a GraphQL string.