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
DocumentNodes are created by the user, for example by using thegqltemplate literal tag. This is theQUERY,MUTATION, orSUBSCRIPTIONargument passed into auseQueryhook or as thequeryoption toclient.query.Transformed
DocumentNodes are derived from user-suppliedDocumentNodes, for example, by applyingDocumentTransforms 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.
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 DocumentNodes.
Read more...
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.
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 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.
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 DocumentNodes.
Read more...
This cache is used to cache the hashes of persisted queries.
A cache inside of QueryManager.
It is called with transformed DocumentNodes.
Cache used in removeTypenameFromVariables.
This function is called transformed DocumentNodes.
numberCache 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.
numberUsed by the internal checkDocument that traverses GraphQL documents and throws an error if the document is invalid.
if they are not valid.
numberCache size for the print function.
It is called with transformed DocumentNodes.
Read more...
This method is called to transform a GraphQL query AST parsed by gql
back into a GraphQL string.