Skip to content

Commit

Permalink
Add a "memory management" documentation page (#11415)
Browse files Browse the repository at this point in the history
Co-authored-by: Maria Elisabeth Schreiber <maria.schreiber@apollographql.com>
Co-authored-by: Jerel Miller <jerelmiller@gmail.com>
Co-authored-by: jerelmiller <jerelmiller@users.noreply.github.com>
  • Loading branch information
4 people authored Dec 20, 2023
1 parent ff5a332 commit de5b878
Show file tree
Hide file tree
Showing 19 changed files with 386 additions and 118 deletions.
2 changes: 0 additions & 2 deletions .api-reports/api-report-core.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,6 @@ export class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
getResolvers(): Resolvers;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-react.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-react_components.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,8 +120,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-react_context.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-react_hoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-react_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-react_internal.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-react_ssr.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-testing_core.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report-utilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,6 @@ class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
// Warning: (ae-forgotten-export) The symbol "RefetchQueriesInclude" needs to be exported by the entry point index.d.ts
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
Expand Down
2 changes: 0 additions & 2 deletions .api-reports/api-report.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ export class ApolloClient<TCacheShape> implements DataProxy {
get documentTransform(): DocumentTransform;
extract(optimistic?: boolean): TCacheShape;
// Warning: (ae-forgotten-export) The symbol "getApolloClientMemoryInternals" needs to be exported by the entry point index.d.ts
//
// @internal
getMemoryInternals?: typeof getApolloClientMemoryInternals;
getObservableQueries(include?: RefetchQueriesInclude): Map<string, ObservableQuery<any>>;
getResolvers(): Resolvers;
Expand Down
106 changes: 69 additions & 37 deletions config/apiExtractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
IConfigFile,
} from "@microsoft/api-extractor";
import { parseArgs } from "node:util";
import fs from "node:fs";

// @ts-ignore
import { map } from "./entryPoints.js";
Expand Down Expand Up @@ -40,37 +41,67 @@ const packageJsonFullPath = path.resolve(__dirname, "../package.json");

process.exitCode = 0;

map((entryPoint: { dirs: string[] }) => {
if (entryPoint.dirs.length > 0 && parsed.values["main-only"]) return;
const tempDir = fs.mkdtempSync("api-model");
try {
if (parsed.values.generate?.includes("docModel")) {
console.log(
"\n\nCreating API extractor docmodel for the a combination of all entry points"
);
const dist = path.resolve(__dirname, "../dist");
const entryPoints = map((entryPoint: { dirs: string[] }) => {
return `export * from "${dist}/${entryPoint.dirs.join("/")}/index.d.ts";`;
}).join("\n");
const entryPointFile = path.join(tempDir, "entry.d.ts");
fs.writeFileSync(entryPointFile, entryPoints);

buildReport(entryPointFile, "docModel");
}

const path = entryPoint.dirs.join("/");
const mainEntryPointFilePath =
`<projectFolder>/dist/${path}/index.d.ts`.replace("//", "/");
console.log(
"\n\nCreating API extractor report for " + mainEntryPointFilePath
);
if (parsed.values.generate?.includes("apiReport")) {
map((entryPoint: { dirs: string[] }) => {
const path = entryPoint.dirs.join("/");
const mainEntryPointFilePath =
`<projectFolder>/dist/${path}/index.d.ts`.replace("//", "/");
console.log(
"\n\nCreating API extractor report for " + mainEntryPointFilePath
);
buildReport(
mainEntryPointFilePath,
"apiReport",
`api-report${path ? "-" + path.replace(/\//g, "_") : ""}.md`
);
});
}
} finally {
fs.rmSync(tempDir, { recursive: true });
}

function buildReport(
mainEntryPointFilePath: string,
mode: "apiReport" | "docModel",
reportFileName = ""
) {
const configObject: IConfigFile = {
...(JSON.parse(JSON.stringify(baseConfig)) as IConfigFile),
mainEntryPointFilePath,
};

configObject.apiReport!.reportFileName = `api-report${
path ? "-" + path.replace(/\//g, "_") : ""
}.md`;

configObject.apiReport!.enabled =
parsed.values.generate?.includes("apiReport") || false;

configObject.docModel!.enabled =
parsed.values.generate?.includes("docModel") || false;

if (entryPoint.dirs.length !== 0) {
if (mode === "apiReport") {
configObject.apiReport!.enabled = true;
configObject.docModel = { enabled: false };
configObject.tsdocMetadata = { enabled: false };
configObject.messages!.extractorMessageReporting![
"ae-unresolved-link"
]!.logLevel = ExtractorLogLevel.None;
configObject.apiReport!.reportFileName = reportFileName;
} else {
configObject.docModel!.enabled = true;
configObject.apiReport = {
enabled: false,
// this has to point to an existing folder, otherwise the extractor will fail
// but it will not write the file
reportFileName: "disabled.md",
reportFolder: tempDir,
};
}

const extractorConfig = ExtractorConfig.prepare({
Expand All @@ -85,22 +116,23 @@ map((entryPoint: { dirs: string[] }) => {
});

let succeededAdditionalChecks = true;
const contents = readFileSync(extractorConfig.reportFilePath, "utf8");

if (contents.includes("rehackt")) {
succeededAdditionalChecks = false;
console.error(
"❗ %s contains a reference to the `rehackt` package!",
extractorConfig.reportFilePath
);
}
if (contents.includes('/// <reference types="react" />')) {
succeededAdditionalChecks = false;
console.error(
"❗ %s contains a reference to the global `React` type!/n" +
'Use `import type * as ReactTypes from "react";` instead',
extractorConfig.reportFilePath
);
if (fs.existsSync(extractorConfig.reportFilePath)) {
const contents = readFileSync(extractorConfig.reportFilePath, "utf8");
if (contents.includes("rehackt")) {
succeededAdditionalChecks = false;
console.error(
"❗ %s contains a reference to the `rehackt` package!",
extractorConfig.reportFilePath
);
}
if (contents.includes('/// <reference types="react" />')) {
succeededAdditionalChecks = false;
console.error(
"❗ %s contains a reference to the global `React` type!/n" +
'Use `import type * as ReactTypes from "react";` instead',
extractorConfig.reportFilePath
);
}
}

if (extractorResult.succeeded && succeededAdditionalChecks) {
Expand All @@ -115,4 +147,4 @@ map((entryPoint: { dirs: string[] }) => {
}
process.exitCode = 1;
}
});
}
2 changes: 1 addition & 1 deletion docs/shared/ApiDoc/DocBlock.js
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export function Example({
if (!value) return null;
return (
<MaybeCollapsible collapsible={collapsible}>
<b>{mdToReact(value)}</b>
{mdToReact(value)}
</MaybeCollapsible>
);
}
Expand Down
7 changes: 6 additions & 1 deletion docs/shared/ApiDoc/PropertySignatureTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,12 @@ export function PropertySignatureTable({
</MDX.inlineCode>
</GridItem>
<GridItem className="cell" fontSize="md" lineHeight="base">
<DocBlock canonicalReference={property.canonicalReference} />
<DocBlock
canonicalReference={property.canonicalReference}
summary
remarks
remarkCollapsible
/>
</GridItem>
</React.Fragment>
))}
Expand Down
126 changes: 126 additions & 0 deletions docs/source/caching/memory-management.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
---
title: Memory management
api_doc:
- "@apollo/client!CacheSizes:interface"
- "@apollo/client!ApolloClient:class"
subtitle: Learn how to choose and set custom cache sizes
description: Learn how to choose and set custom cache sizes with Apollo Client.
minVersion: 3.9.0
---

import { Remarks, PropertySignatureTable, Example } from '../../shared/ApiDoc';

## 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](https://en.wikipedia.org/wiki/Weak_reference), 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)](#setting-cache-sizes-before-loading-the-apollo-client-library) or [after](#adjusting-cache-sizes-after-loading-the-apollo-client-library) 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.

```ts
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.

```js
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

<Remarks
canonicalReference="@apollo/client!CacheSizes:interface"
/>

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&mdash;
a `DocumentNode`. There are two types:

* **User-supplied `DocumentNode`s** are created
by the user, for example by using the `gql` template literal tag.
This is the `QUERY`, `MUTATION`, or `SUBSCRIPTION` argument passed
into a [`useQuery` hook](../data/queries/#usequery-api) or as the `query` option to `client.query`.
* **Transformed `DocumentNode`s** are derived from
user-supplied `DocumentNode`s, for example, by applying [`DocumentTransform`s](../data/document-transforms/) 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.<br />
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.<br />
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.

</Note>

<Example
canonicalReference="@apollo/client!ApolloClient#getMemoryInternals:member"
index={0}
/>

<Example
collapsible
canonicalReference="@apollo/client!ApolloClient#getMemoryInternals:member"
index={1}
/>

### Cache options

<PropertySignatureTable
canonicalReference="@apollo/client!CacheSizes:interface"
properties
/>
Loading

0 comments on commit de5b878

Please sign in to comment.