From 1a49357cc433f22bb4132ee500c37f4d8a12f7de Mon Sep 17 00:00:00 2001 From: Owen Allen Date: Mon, 23 Aug 2021 16:05:49 -0700 Subject: [PATCH 1/4] Allows documentStore to be specified in the ApolloServer constructor. --- docs/source/api/apollo-server.md | 16 ++ .../apollo-server-core/src/ApolloServer.ts | 14 +- .../src/__tests__/documentStore.test.ts | 137 ++++++++++++++++++ .../apollo-server-core/src/graphqlOptions.ts | 5 +- .../apollo-server-core/src/requestPipeline.ts | 9 +- packages/apollo-server-core/src/types.ts | 6 +- 6 files changed, 175 insertions(+), 12 deletions(-) create mode 100644 packages/apollo-server-core/src/__tests__/documentStore.test.ts diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md index a22bc638ba7..d7846a2c3fc 100644 --- a/docs/source/api/apollo-server.md +++ b/docs/source/api/apollo-server.md @@ -189,6 +189,22 @@ An array containing custom functions to use as additional [validation rules](htt + + + +##### `documentStore` + +`KeyValueCache` or `boolean` + + + +The server checks the SHA-256 hash of each incoming operation against DocumentNodes cached in the `documentStore` and skips unnecessary parsing and validation if a match is found. The `documentStore` does not store the results of queries. Set this value when you want to change the cache size or store the cache information in an alternate location. Do not re-use a cache between multiple `ApolloServer` instances unless you prefix the entries uniquely per `ApolloServer` (for example using [PrefixingKeyValueCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/PrefixingKeyValueCache.ts)). + +The default value is an [InMemoryLRUCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/InMemoryLRUCache.ts) with a default size of 30MiB, which is usually sufficient unless the server processes a large number of unique operations. Pass `false` to disable caching entirely. + + + + diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index 1f9c420464b..f82a6349304 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -28,6 +28,7 @@ import type { Config, Context, ContextFunction, + DocumentStore, PluginDefinition, } from './types'; @@ -81,7 +82,7 @@ export type SchemaDerivedData = { // A store that, when enabled (default), will store the parsed and validated // versions of operations in-memory, allowing subsequent parses/validates // on the same operation to be executed immediately. - documentStore?: InMemoryLRUCache; + documentStore?: DocumentStore; }; type ServerState = @@ -172,6 +173,7 @@ export class ApolloServerBase< mocks, mockEntireSchema, experimental_approximateDocumentStoreMiB, + documentStore, ...requestOptions } = this.config; @@ -671,8 +673,14 @@ export class ApolloServerBase< private generateSchemaDerivedData(schema: GraphQLSchema): SchemaDerivedData { const schemaHash = generateSchemaHash(schema!); - // Initialize the document store. This cannot currently be disabled. - const documentStore = this.initializeDocumentStore(); + // normalize documentStore so it's either a DocumentStore or undefined + const configValue = this.config.documentStore; + const documentStore = + configValue === undefined || configValue === true + ? this.initializeDocumentStore() + : configValue === false + ? undefined + : configValue; return { schema, diff --git a/packages/apollo-server-core/src/__tests__/documentStore.test.ts b/packages/apollo-server-core/src/__tests__/documentStore.test.ts new file mode 100644 index 00000000000..11419e56162 --- /dev/null +++ b/packages/apollo-server-core/src/__tests__/documentStore.test.ts @@ -0,0 +1,137 @@ +import gql from 'graphql-tag'; +import type { DocumentNode } from 'graphql'; + +import { ApolloServerBase } from '../ApolloServer'; +import { InMemoryLRUCache } from 'apollo-server-caching'; + +const typeDefs = gql` + type Query { + hello: String + } +`; + +const resolvers = { + Query: { + hello() { + return 'world'; + }, + }, +}; + +// allow us to access internals of the class +class ApolloServerObservable extends ApolloServerBase { + override graphQLServerOptions() { + return super.graphQLServerOptions(); + } +} + +const documentNodeMatcher = { + kind: 'Document', + definitions: expect.any(Array), + loc: { + start: 0, + end: 15, + }, +}; + +const operations = { + simple: { + op: { query: 'query { hello }' }, + hash: 'ec2e01311ab3b02f3d8c8c712f9e579356d332cd007ac4c1ea5df727f482f05f', + }, +}; + +describe('ApolloServerBase documentStore', () => { + it('documentStore - undefined', async () => { + const server = new ApolloServerObservable({ + typeDefs, + resolvers, + }); + + await server.start(); + + const options = await server.graphQLServerOptions(); + const embeddedStore = options.documentStore as any; + expect(embeddedStore).toBeInstanceOf(InMemoryLRUCache); + + await server.executeOperation(operations.simple.op); + + expect(await embeddedStore.getTotalSize()).toBe(403); + expect(await embeddedStore.get(operations.simple.hash)).toMatchObject( + documentNodeMatcher, + ); + }); + + it('documentStore - custom', async () => { + const documentStore = { + get: async function (key: string) { + return cache[key]; + }, + set: async function (key: string, val: DocumentNode) { + cache[key] = val; + }, + delete: async function () {}, + }; + const cache: Record = {}; + + const getSpy = jest.spyOn(documentStore, 'get'); + const setSpy = jest.spyOn(documentStore, 'set'); + + const server = new ApolloServerBase({ + typeDefs, + resolvers, + documentStore, + }); + await server.start(); + + await server.executeOperation(operations.simple.op); + + expect(Object.keys(cache)).toEqual([operations.simple.hash]); + expect(cache[operations.simple.hash]).toMatchObject(documentNodeMatcher); + + await server.executeOperation(operations.simple.op); + + expect(Object.keys(cache)).toEqual([operations.simple.hash]); + + expect(getSpy.mock.calls.length).toBe(2); + expect(setSpy.mock.calls.length).toBe(1); + }); + + it('documentStore - false', async () => { + const server = new ApolloServerObservable({ + typeDefs, + resolvers, + documentStore: false, + }); + + await server.start(); + + const options = await server.graphQLServerOptions(); + expect(options.documentStore).toBe(undefined); + + const result = await server.executeOperation(operations.simple.op); + + expect(result.data).toEqual({ hello: 'world' }); + }); + + it('documentStore - true', async () => { + const server = new ApolloServerObservable({ + typeDefs, + resolvers, + documentStore: true, + }); + + await server.start(); + + const options = await server.graphQLServerOptions(); + const embeddedStore = options.documentStore as any; + expect(embeddedStore).toBeInstanceOf(InMemoryLRUCache); + + await server.executeOperation(operations.simple.op); + + expect(await embeddedStore.getTotalSize()).toBe(403); + expect(await embeddedStore.get(operations.simple.hash)).toMatchObject( + documentNodeMatcher, + ); + }); +}); diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts index 61453a2f96e..8020dd47f54 100644 --- a/packages/apollo-server-core/src/graphqlOptions.ts +++ b/packages/apollo-server-core/src/graphqlOptions.ts @@ -7,7 +7,7 @@ import type { GraphQLFormattedError, ParseOptions, } from 'graphql'; -import type { KeyValueCache, InMemoryLRUCache } from 'apollo-server-caching'; +import type { KeyValueCache } from 'apollo-server-caching'; import type { DataSource } from 'apollo-datasource'; import type { ApolloServerPlugin } from 'apollo-server-plugin-base'; import type { @@ -18,6 +18,7 @@ import type { Logger, SchemaHash, } from 'apollo-server-types'; +import type { DocumentStore } from './types'; /* * GraphQLServerOptions @@ -56,7 +57,7 @@ export interface GraphQLServerOptions< cache?: KeyValueCache; persistedQueries?: PersistedQueryOptions; plugins?: ApolloServerPlugin[]; - documentStore?: InMemoryLRUCache; + documentStore?: DocumentStore; parseOptions?: ParseOptions; nodeEnv?: string; } diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index 4f04118cc93..8945c7aa5b4 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -53,16 +53,13 @@ import type { } from 'apollo-server-plugin-base'; import { Dispatcher } from './utils/dispatcher'; -import { - InMemoryLRUCache, - KeyValueCache, - PrefixingKeyValueCache, -} from 'apollo-server-caching'; +import { KeyValueCache, PrefixingKeyValueCache } from 'apollo-server-caching'; export { GraphQLRequest, GraphQLResponse, GraphQLRequestContext }; import createSHA from './utils/createSHA'; import { HttpQueryError } from './runHttpQuery'; +import type { DocumentStore } from './types'; import { Headers } from 'apollo-server-env'; export const APQ_CACHE_PREFIX = 'apq:'; @@ -90,7 +87,7 @@ export interface GraphQLRequestPipelineConfig { ) => GraphQLResponse | null; plugins?: ApolloServerPlugin[]; - documentStore?: InMemoryLRUCache; + documentStore?: DocumentStore; parseOptions?: ParseOptions; } diff --git a/packages/apollo-server-core/src/types.ts b/packages/apollo-server-core/src/types.ts index c2d73c7afe0..c324ab47385 100644 --- a/packages/apollo-server-core/src/types.ts +++ b/packages/apollo-server-core/src/types.ts @@ -18,7 +18,8 @@ import type { GraphQLSchemaModule } from '@apollographql/apollo-tools'; export type { GraphQLSchemaModule }; -export { KeyValueCache } from 'apollo-server-caching'; +import type { KeyValueCache } from 'apollo-server-caching'; +export type { KeyValueCache }; export type Context = T; export type ContextFunction = ( @@ -81,6 +82,8 @@ export interface GatewayInterface { // that older versions of `@apollo/gateway` build against AS3. export interface GraphQLService extends GatewayInterface {} +export type DocumentStore = KeyValueCache; + // This configuration is shared between all integrations and should include // fields that are not specific to a single integration export interface Config extends BaseConfig { @@ -100,4 +103,5 @@ export interface Config extends BaseConfig { stopOnTerminationSignals?: boolean; apollo?: ApolloConfigInput; nodeEnv?: string; + documentStore?: DocumentStore | boolean; } From d226e9bc55978aa267042608a9d4d7db9f43c9e0 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 1 Oct 2021 13:03:58 -0700 Subject: [PATCH 2/4] Some edits from review: - Remove `experimental_approximateDocumentStoreMiB` (but add InMemoryLRUCache.jsonBytesSizeCalculator which makes it easier to reproduce) - Remove `documentStore: boolean`; instead `documentStore: null` --- .../src/InMemoryLRUCache.ts | 7 ++++ .../apollo-server-core/src/ApolloServer.ts | 36 ++++++++----------- .../src/__tests__/documentStore.test.ts | 27 ++------------ .../src/__tests__/runQuery.test.ts | 12 ++----- .../apollo-server-core/src/graphqlOptions.ts | 2 +- .../apollo-server-core/src/requestPipeline.ts | 2 +- packages/apollo-server-core/src/types.ts | 3 +- 7 files changed, 30 insertions(+), 59 deletions(-) diff --git a/packages/apollo-server-caching/src/InMemoryLRUCache.ts b/packages/apollo-server-caching/src/InMemoryLRUCache.ts index e181c5abd30..fe6fa55ea3a 100644 --- a/packages/apollo-server-caching/src/InMemoryLRUCache.ts +++ b/packages/apollo-server-caching/src/InMemoryLRUCache.ts @@ -50,4 +50,11 @@ export class InMemoryLRUCache implements KeyValueCache { async getTotalSize() { return this.store.length; } + + // This is a size calculator based on the number of bytes in a JSON + // encoding of the stored object. It happens to be what ApolloServer + // uses for its default DocumentStore and may be helpful to others as well. + static jsonBytesSizeCalculator(obj: T): number { + return Buffer.byteLength(JSON.stringify(obj), 'utf8'); + } } diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts index f82a6349304..652e19652ef 100644 --- a/packages/apollo-server-core/src/ApolloServer.ts +++ b/packages/apollo-server-core/src/ApolloServer.ts @@ -72,17 +72,13 @@ const NoIntrospection = (context: ValidationContext) => ({ }, }); -function approximateObjectSize(obj: T): number { - return Buffer.byteLength(JSON.stringify(obj), 'utf8'); -} - export type SchemaDerivedData = { schema: GraphQLSchema; schemaHash: SchemaHash; // A store that, when enabled (default), will store the parsed and validated // versions of operations in-memory, allowing subsequent parses/validates // on the same operation to be executed immediately. - documentStore?: DocumentStore; + documentStore: DocumentStore | null; }; type ServerState = @@ -145,7 +141,6 @@ export class ApolloServerBase< private toDispose = new Set<() => Promise>(); private toDisposeLast = new Set<() => Promise>(); private drainServers: (() => Promise) | null = null; - private experimental_approximateDocumentStoreMiB: Config['experimental_approximateDocumentStoreMiB']; private stopOnTerminationSignals: boolean; private landingPage: LandingPage | null = null; @@ -172,7 +167,6 @@ export class ApolloServerBase< // requestOptions. mocks, mockEntireSchema, - experimental_approximateDocumentStoreMiB, documentStore, ...requestOptions } = this.config; @@ -208,8 +202,6 @@ export class ApolloServerBase< this.parseOptions = parseOptions; this.context = context; - this.experimental_approximateDocumentStoreMiB = - experimental_approximateDocumentStoreMiB; const isDev = this.config.nodeEnv !== 'production'; @@ -673,19 +665,18 @@ export class ApolloServerBase< private generateSchemaDerivedData(schema: GraphQLSchema): SchemaDerivedData { const schemaHash = generateSchemaHash(schema!); - // normalize documentStore so it's either a DocumentStore or undefined - const configValue = this.config.documentStore; - const documentStore = - configValue === undefined || configValue === true - ? this.initializeDocumentStore() - : configValue === false - ? undefined - : configValue; - return { schema, schemaHash, - documentStore, + // The DocumentStore is schema-derived because we put documents in it after + // checking that they pass GraphQL validation against the schema and use + // this to skip validation as well as parsing. So we can't reuse the same + // DocumentStore for different schemas because that might make us treat + // invalid operations as valid. + documentStore: + this.config.documentStore === undefined + ? this.initializeDocumentStore() + : this.config.documentStore, }; } @@ -883,9 +874,10 @@ export class ApolloServerBase< // only using JSON.stringify on the DocumentNode (and thus doesn't account // for unicode characters, etc.), but it should do a reasonable job at // providing a caching document store for most operations. - maxSize: - Math.pow(2, 20) * (this.experimental_approximateDocumentStoreMiB || 30), - sizeCalculator: approximateObjectSize, + // + // If you want to tweak the max size, pass in your own documentStore. + maxSize: Math.pow(2, 20) * 30, + sizeCalculator: InMemoryLRUCache.jsonBytesSizeCalculator, }); } diff --git a/packages/apollo-server-core/src/__tests__/documentStore.test.ts b/packages/apollo-server-core/src/__tests__/documentStore.test.ts index 11419e56162..6eb5825139d 100644 --- a/packages/apollo-server-core/src/__tests__/documentStore.test.ts +++ b/packages/apollo-server-core/src/__tests__/documentStore.test.ts @@ -97,41 +97,20 @@ describe('ApolloServerBase documentStore', () => { expect(setSpy.mock.calls.length).toBe(1); }); - it('documentStore - false', async () => { + it('documentStore - null', async () => { const server = new ApolloServerObservable({ typeDefs, resolvers, - documentStore: false, + documentStore: null, }); await server.start(); const options = await server.graphQLServerOptions(); - expect(options.documentStore).toBe(undefined); + expect(options.documentStore).toBe(null); const result = await server.executeOperation(operations.simple.op); expect(result.data).toEqual({ hello: 'world' }); }); - - it('documentStore - true', async () => { - const server = new ApolloServerObservable({ - typeDefs, - resolvers, - documentStore: true, - }); - - await server.start(); - - const options = await server.graphQLServerOptions(); - const embeddedStore = options.documentStore as any; - expect(embeddedStore).toBeInstanceOf(InMemoryLRUCache); - - await server.executeOperation(operations.simple.op); - - expect(await embeddedStore.getTotalSize()).toBe(403); - expect(await embeddedStore.get(operations.simple.hash)).toMatchObject( - documentNodeMatcher, - ); - }); }); diff --git a/packages/apollo-server-core/src/__tests__/runQuery.test.ts b/packages/apollo-server-core/src/__tests__/runQuery.test.ts index 1d6c8a5c878..9c9152a4353 100644 --- a/packages/apollo-server-core/src/__tests__/runQuery.test.ts +++ b/packages/apollo-server-core/src/__tests__/runQuery.test.ts @@ -1107,12 +1107,6 @@ describe('runQuery', () => { return '{\n' + query + '}'; } - // This should use the same logic as the calculation in InMemoryLRUCache: - // https://github.com/apollographql/apollo-server/blob/94b98ff3/packages/apollo-server-caching/src/InMemoryLRUCache.ts#L23 - function approximateObjectSize(obj: T): number { - return Buffer.byteLength(JSON.stringify(obj), 'utf8'); - } - it('validates each time when the documentStore is not present', async () => { expect.assertions(4); @@ -1167,12 +1161,12 @@ describe('runQuery', () => { // size of the two smaller queries. All three of these queries will never // fit into this cache, so we'll roll through them all. const maxSize = - approximateObjectSize(parse(querySmall1)) + - approximateObjectSize(parse(querySmall2)); + InMemoryLRUCache.jsonBytesSizeCalculator(parse(querySmall1)) + + InMemoryLRUCache.jsonBytesSizeCalculator(parse(querySmall2)); const documentStore = new InMemoryLRUCache({ maxSize, - sizeCalculator: approximateObjectSize, + sizeCalculator: InMemoryLRUCache.jsonBytesSizeCalculator, }); await runRequest({ plugins, documentStore, queryString: querySmall1 }); diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts index 8020dd47f54..2632fb59c39 100644 --- a/packages/apollo-server-core/src/graphqlOptions.ts +++ b/packages/apollo-server-core/src/graphqlOptions.ts @@ -57,7 +57,7 @@ export interface GraphQLServerOptions< cache?: KeyValueCache; persistedQueries?: PersistedQueryOptions; plugins?: ApolloServerPlugin[]; - documentStore?: DocumentStore; + documentStore?: DocumentStore | null; parseOptions?: ParseOptions; nodeEnv?: string; } diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts index 8945c7aa5b4..6b34182c045 100644 --- a/packages/apollo-server-core/src/requestPipeline.ts +++ b/packages/apollo-server-core/src/requestPipeline.ts @@ -87,7 +87,7 @@ export interface GraphQLRequestPipelineConfig { ) => GraphQLResponse | null; plugins?: ApolloServerPlugin[]; - documentStore?: DocumentStore; + documentStore?: DocumentStore | null; parseOptions?: ParseOptions; } diff --git a/packages/apollo-server-core/src/types.ts b/packages/apollo-server-core/src/types.ts index c324ab47385..9e3c8e5b143 100644 --- a/packages/apollo-server-core/src/types.ts +++ b/packages/apollo-server-core/src/types.ts @@ -99,9 +99,8 @@ export interface Config extends BaseConfig { plugins?: PluginDefinition[]; persistedQueries?: PersistedQueryOptions | false; gateway?: GatewayInterface; - experimental_approximateDocumentStoreMiB?: number; stopOnTerminationSignals?: boolean; apollo?: ApolloConfigInput; nodeEnv?: string; - documentStore?: DocumentStore | boolean; + documentStore?: DocumentStore | null; } From 383b96146ff8f98b85b6836f08298caeac569679 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Fri, 1 Oct 2021 13:23:33 -0700 Subject: [PATCH 3/4] docs and CHANGELOG --- CHANGELOG.md | 17 +++++++++-- docs/source/api/apollo-server.md | 49 +++++++++++--------------------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aa1ae61184..9eab7b5852e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,21 @@ The version headers in this history reflect the versions of Apollo Server itself - [`@apollo/gateway`](https://github.com/apollographql/federation/blob/HEAD/gateway-js/CHANGELOG.md) - [`@apollo/federation`](https://github.com/apollographql/federation/blob/HEAD/federation-js/CHANGELOG.md) -## vNEXT - +## vNEXT (minor) + +- `apollo-server-core`: You can now specify your own `DocumentStore` (a `KeyValueStore`) for Apollo Server's cache of parsed and validated GraphQL operation abstract syntax trees via the new `documentStore` constructor option. This replaces the `experimental_approximateDocumentStoreMiB` option. You can replace `new ApolloServer({experimental_approximateDocumentStoreMiB: approximateDocumentStoreMiB, ...moreOptions})` with: + ```typescript + import { InMemoryLRUCache } from 'apollo-server-caching'; + import type { DocumentNode } from 'graphql'; + new ApolloServer({ + documentStore: new InMemoryLRUCache({ + maxSize: Math.pow(2, 20) * approximateDocumentStoreMiB, + sizeCalculator: InMemoryLRUCache.jsonBytesSizeCalculator, + }), + ...moreOptions, + }) + ``` + [PR #5644](https://github.com/apollographql/apollo-server/pull/5644) [Issue #5634](https://github.com/apollographql/apollo-server/issues/5634) - `apollo-server-core`: For ease of testing, you can specify the node environment via `new ApolloServer({nodeEnv})` in addition to via the `NODE_ENV` environment variable. The environment variable is now only read during server startup (and in some error cases) rather than on every request. [PR #5657](https://github.com/apollographql/apollo-server/pull/5657) - `apollo-server-koa`: The peer dependency on `koa` (added in v3.0.0) should be a `^` range dependency rather than depending on exactly one version, and it should not be automatically increased when new versions of `koa` are released. [PR #5759](https://github.com/apollographql/apollo-server/pull/5759) - `apollo-server-fastify`: Export `ApolloServerFastifyConfig` and `FastifyContext` TypeScript types. [PR #5743](https://github.com/apollographql/apollo-server/pull/5743) diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md index d7846a2c3fc..73cda0a8660 100644 --- a/docs/source/api/apollo-server.md +++ b/docs/source/api/apollo-server.md @@ -194,13 +194,27 @@ An array containing custom functions to use as additional [validation rules](htt ##### `documentStore` -`KeyValueCache` or `boolean` +`KeyValueCache` or `null` -The server checks the SHA-256 hash of each incoming operation against DocumentNodes cached in the `documentStore` and skips unnecessary parsing and validation if a match is found. The `documentStore` does not store the results of queries. Set this value when you want to change the cache size or store the cache information in an alternate location. Do not re-use a cache between multiple `ApolloServer` instances unless you prefix the entries uniquely per `ApolloServer` (for example using [PrefixingKeyValueCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/PrefixingKeyValueCache.ts)). +The server checks the SHA-256 hash of each incoming operation against DocumentNodes cached in the `documentStore` and skips unnecessary parsing and validation if a match is found. The `documentStore` does not store the results of queries, just the operation's abstract syntax tree. Set this value when you want to change the cache size or store the cache information in an alternate location. -The default value is an [InMemoryLRUCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/InMemoryLRUCache.ts) with a default size of 30MiB, which is usually sufficient unless the server processes a large number of unique operations. Pass `false` to disable caching entirely. +**Do not share a document store between multiple `ApolloServer` instances** unless you prefix the entries uniquely per `ApolloServer` (for example using [PrefixingKeyValueCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/PrefixingKeyValueCache.ts)): Apollo Server assumes any operation found in the document store has been validated against the current schema, so if multiple servers with different schemas share the same document store, your server may execute invalid operations. + +The default value is an [InMemoryLRUCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/InMemoryLRUCache.ts) with an approximate size of 30MiB, which is usually sufficient unless the server processes a large number of unique operations. If you just want to change the size, pass: +```typescript +import { InMemoryLRUCache } from 'apollo-server-caching'; +import type { DocumentNode } from 'graphql'; +new ApolloServer({ + documentStore: new InMemoryLRUCache({ + maxSize: Math.pow(2, 20) * approximateDocumentStoreMiB, + sizeCalculator: InMemoryLRUCache.jsonBytesSizeCalculator, + }), +}) +``` + +Pass `null` to disable this document cache entirely. @@ -422,35 +436,6 @@ If this is set to any string value, use that value instead of the environment va -### Experimental options - -**These options are experimental.** They might be removed or change at any time, even within a patch release. - - - - - - - - - - - - - - -
Name /
Type
Description
- -##### `experimental_approximateDocumentStoreMiB` - -`number` - - -Sets the approximate size (in MiB) of the server's `DocumentNode` cache. The server checks the SHA-256 hash of each incoming operation against cached `DocumentNode`s, and skips unnecessary parsing and validation if a match is found. - -The cache's default size is 30MiB, which is usually sufficient unless the server processes a large number of unique operations. -
- ### Middleware-specific `context` fields The `context` object passed between Apollo Server resolvers automatically includes certain fields, depending on which [Node.js middleware](../integrations/middleware/) you're using: From 76d234eb405baf8576a0a00f1654ba9a4bfa9782 Mon Sep 17 00:00:00 2001 From: Stephen Barlow Date: Fri, 1 Oct 2021 15:52:57 -0700 Subject: [PATCH 4/4] Edits to documentStore reference --- docs/source/api/apollo-server.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md index 73cda0a8660..e8a9498ae95 100644 --- a/docs/source/api/apollo-server.md +++ b/docs/source/api/apollo-server.md @@ -198,11 +198,16 @@ An array containing custom functions to use as additional [validation rules](htt -The server checks the SHA-256 hash of each incoming operation against DocumentNodes cached in the `documentStore` and skips unnecessary parsing and validation if a match is found. The `documentStore` does not store the results of queries, just the operation's abstract syntax tree. Set this value when you want to change the cache size or store the cache information in an alternate location. +A key-value cache that Apollo Server uses to store previously encountered GraphQL operations (as `DocumentNode`s). It does _not_ store query _results_. -**Do not share a document store between multiple `ApolloServer` instances** unless you prefix the entries uniquely per `ApolloServer` (for example using [PrefixingKeyValueCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/PrefixingKeyValueCache.ts)): Apollo Server assumes any operation found in the document store has been validated against the current schema, so if multiple servers with different schemas share the same document store, your server may execute invalid operations. +Whenever Apollo Server receives an incoming operation, it checks whether that exact operation is present in its `documentStore`. If it's present, Apollo Server can safely skip parsing and validating the operation, thereby improving performance. + +The default `documentStore` is an [`InMemoryLRUCache`](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/InMemoryLRUCache.ts) with an approximate size of 30MiB. This is usually sufficient unless the server processes a large number of unique operations. Provide this option if you want to change the cache size or store the cache information in an alternate location. + +To use `InMemoryLRUCache` but change its size to an amount `approximateDocumentStoreMiB`: + +
-The default value is an [InMemoryLRUCache](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/InMemoryLRUCache.ts) with an approximate size of 30MiB, which is usually sufficient unless the server processes a large number of unique operations. If you just want to change the size, pass: ```typescript import { InMemoryLRUCache } from 'apollo-server-caching'; import type { DocumentNode } from 'graphql'; @@ -214,7 +219,13 @@ new ApolloServer({ }) ``` -Pass `null` to disable this document cache entirely. +
+ +**Do not share a `documentStore` between multiple `ApolloServer` instances**, _unless_ you assign a unique prefix to each instance's entries (for example, using [`PrefixingKeyValueCache`](https://github.com/apollographql/apollo-server/blob/main/packages/apollo-server-caching/src/PrefixingKeyValueCache.ts)). Apollo Server skips parsing and validating any operation that's present in its `documentStore`, so if servers with _different_ schemas share the _same_ `documentStore`, a server might execute an operation that its schema doesn't support. + +Pass `null` to disable this cache entirely. + +Available in Apollo Server v3.4.0 and later.