Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache successfully parsed and validated documents for future requests. #2111

Merged
merged 15 commits into from
Jan 23, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### vNEXT

- Implement an in-memory cache store to save parsed and validated documents and provide performance benefits for successful executions of the same document. [PR #2111](https://github.com/apollographql/apollo-server/pull/2111) (`2.4.0-alpha.0`)
- Switch from `json-stable-stringify` to `fast-json-stable-stringify`. [PR #2065](https://github.com/apollographql/apollo-server/pull/2065)

### v2.3.1
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-cache-control/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-cache-control",
"version": "0.4.0",
"version": "0.5.0-alpha.1",
"description": "A GraphQL extension for cache control",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-datasource-rest/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-datasource-rest",
"version": "0.2.1",
"version": "0.3.0-alpha.1",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-datasource/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-datasource",
"version": "0.2.1",
"version": "0.3.0-alpha.1",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-engine-reporting/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-engine-reporting",
"version": "0.2.0",
"version": "0.3.0-alpha.1",
"description": "Send reports about your GraphQL services to Apollo Engine",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-azure-functions/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-azure-functions",
"version": "2.3.1",
"version": "2.4.0-alpha.2",
"description": "Production-ready Node.js GraphQL server for Azure Functions",
"keywords": [
"GraphQL",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-cache-memcached/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-cache-memcached",
"version": "0.2.1",
"version": "0.3.0-alpha.1",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-cache-redis/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-cache-redis",
"version": "0.2.1",
"version": "0.3.0-alpha.1",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-caching/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-caching",
"version": "0.2.1",
"version": "0.3.0-alpha.1",
"author": "opensource@apollographql.com",
"license": "MIT",
"repository": {
Expand Down
44 changes: 24 additions & 20 deletions packages/apollo-server-caching/src/InMemoryLRUCache.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,33 @@
import LRU from 'lru-cache';
import { KeyValueCache } from './KeyValueCache';

function defaultLengthCalculation(item: any) {
if (Array.isArray(item) || typeof item === 'string') {
return item.length;
}

// Go with the lru-cache default "naive" size, in lieu anything better:
// https://github.com/isaacs/node-lru-cache/blob/a71be6cd/index.js#L17
return 1;
}

export class InMemoryLRUCache<V = string> implements KeyValueCache<V> {
private store: LRU.Cache<string, V>;

// FIXME: Define reasonable default max size of the cache
constructor({ maxSize = Infinity }: { maxSize?: number } = {}) {
constructor({
maxSize = Infinity,
sizeCalculator = defaultLengthCalculation,
onDispose,
}: {
maxSize?: number;
sizeCalculator?: (value: V, key: string) => number;
onDispose?: (key: string, value: V) => void;
} = {}) {
this.store = new LRU({
max: maxSize,
length(item) {
if (Array.isArray(item) || typeof item === 'string') {
return item.length;
}

// If it's an object, we'll use the length to get an approximate,
// relative size of what it would take to store it. It's certainly not
// 100% accurate, but it's a very, very fast implementation and it
// doesn't require bringing in other dependencies or logic which we need
// to maintain. In the future, we might consider something like:
// npm.im/object-sizeof, but this should be sufficient for now.
if (typeof item === 'object') {
return JSON.stringify(item).length;
}

// Go with the lru-cache default "naive" size, in lieu anything better:
// https://github.com/isaacs/node-lru-cache/blob/a71be6cd/index.js#L17
return 1;
},
length: sizeCalculator,
dispose: onDispose,
});
}

Expand All @@ -43,4 +44,7 @@ export class InMemoryLRUCache<V = string> implements KeyValueCache<V> {
async flush(): Promise<void> {
this.store.reset();
}
async getTotalSize() {
return this.store.length;
}
}
2 changes: 1 addition & 1 deletion packages/apollo-server-cloud-functions/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-cloud-functions",
"version": "2.3.1",
"version": "2.4.0-alpha.2",
"description": "Production-ready Node.js GraphQL server for Google Cloud Functions",
"keywords": [
"GraphQL",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-cloudflare/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-cloudflare",
"version": "2.3.1",
"version": "2.4.0-alpha.2",
"description": "Production-ready Node.js GraphQL server for Cloudflare workers",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion packages/apollo-server-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "apollo-server-core",
"version": "2.3.1",
"version": "2.4.0-alpha.2",
"description": "Core engine for Apollo GraphQL server",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
26 changes: 26 additions & 0 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
GraphQLFieldResolver,
ValidationContext,
FieldDefinitionNode,
DocumentNode,
} from 'graphql';
import { GraphQLExtension } from 'graphql-extensions';
import { EngineReportingAgent } from 'apollo-engine-reporting';
Expand Down Expand Up @@ -92,6 +93,10 @@ function getEngineServiceId(engine: Config['engine']): string | undefined {
const forbidUploadsForTesting =
process && process.env.NODE_ENV === 'test' && !supportsUploadsInNode;

function approximateObjectSize<T>(obj: T): number {
return Buffer.byteLength(JSON.stringify(obj), 'utf8');
}

export class ApolloServerBase {
public subscriptionsPath?: string;
public graphqlPath: string = '/graphql';
Expand All @@ -114,6 +119,11 @@ export class ApolloServerBase {
// the default version is specified in playground.ts
protected playgroundOptions?: PlaygroundRenderPageOptions;

// 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.
private documentStore?: InMemoryLRUCache<DocumentNode>;
abernix marked this conversation as resolved.
Show resolved Hide resolved

// The constructor should be universal across all environments. All environment specific behavior should be set by adding or overriding methods
constructor(config: Config) {
if (!config) throw new Error('ApolloServer requires options.');
Expand All @@ -136,6 +146,9 @@ export class ApolloServerBase {
...requestOptions
} = config;

// Initialize the document store. This cannot currently be disabled.
this.initializeDocumentStore();

// Plugins will be instantiated if they aren't already, and this.plugins
// is populated accordingly.
this.ensurePluginInstantiation(plugins);
Expand Down Expand Up @@ -486,6 +499,18 @@ export class ApolloServerBase {
});
}

private initializeDocumentStore(): void {
this.documentStore = new InMemoryLRUCache<DocumentNode>({
// Create ~about~ a 30MiB InMemoryLRUCache. This is less than precise
// since the technique to calculate the size of a DocumentNode is
// 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) * 30,
sizeCalculator: approximateObjectSize,
});
}

// This function is used by the integrations to generate the graphQLOptions
// from an object containing the request and other integration specific
// options
Expand All @@ -509,6 +534,7 @@ export class ApolloServerBase {
return {
schema: this.schema,
plugins: this.plugins,
documentStore: this.documentStore,
extensions: this.extensions,
context,
// Allow overrides from options. Be explicit about a couple of them to
Expand Down
Loading