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.
1 console.log(client.getMemoryInternals());
Logs output in the following JSON format:
Read more...
1 {
2 "limits": {
3 "canonicalStringify": 1000,
4 "print": 2000,
5 "documentTransform.cache": 2000,
6 "queryManager.getDocumentInfo": 2000,
7 "PersistedQueryLink.persistedQueryHashes": 2000,
8 "fragmentRegistry.transform": 2000,
9 "fragmentRegistry.lookup": 1000,
10 "fragmentRegistry.findFragmentSpreads": 4000,
11 "cache.fragmentQueryDocuments": 1000,
12 "removeTypenameFromVariables.getVariableDefinitions": 2000,
13 "inMemoryCache.maybeBroadcastWatch": 5000,
14 "inMemoryCache.executeSelectionSet": 10000,
15 "inMemoryCache.executeSubSelectedArray": 5000
16 },
17 "sizes": {
18 "canonicalStringify": 4,
19 "print": 14,
20 "addTypenameDocumentTransform": [
21 {
22 "cache": 14
23 }
24 ],
25 "queryManager": {
26 "getDocumentInfo": 14,
27 "documentTransforms": [
28 {
29 "cache": 14
30 },
31 {
32 "cache": 14
33 }
34 ]
35 },
36 "fragmentRegistry": {
37 "findFragmentSpreads": 34,
38 "lookup": 20,
39 "transform": 14
40 },
41 "cache": {
42 "fragmentQueryDocuments": 22
43 },
44 "inMemoryCache": {
45 "executeSelectionSet": 4345,
46 "executeSubSelectedArray": 1206,
47 "maybeBroadcastWatch": 32
48 },
49 "links": [
50 {
51 "PersistedQueryLink": {
52 "persistedQueryHashes": 14
53 }
54 },
55 {
56 "removeTypenameFromVariables": {
57 "getVariableDefinitions": 14
58 }
59 }
60 ]
61 }
62 }
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
.
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
.
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
.
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
Used by the internal checkDocument
that traverses GraphQL documents and throws an error if the document is invalid.
if they are not valid.
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.