Skip to content

Commit

Permalink
Shift the burden of object approximation into the ApolloServerBase
Browse files Browse the repository at this point in the history
…class.

The implementation of object-size approximation which is used for cache
eviction purposes in the `InMemoryLRUCache` implementation (via `lru-cache`)
was a short-term location for extensible logic which is better located
within `ApolloServerBase`.

This is particularly important since future logic may necessitate knowing or
understanding the current size (roughly, memory usage) of the in-memory
storage.  Effective immediately, this adds support for providing a `dispose`
function which is called when an object is purged from the cache to make
room for another.
  • Loading branch information
abernix committed Jan 15, 2019
1 parent 0879a12 commit 2a987a9
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 22 deletions.
41 changes: 21 additions & 20 deletions packages/apollo-server-caching/src/InMemoryLRUCache.ts
@@ -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,
length = defaultLengthCalculation,
dispose,
}: {
maxSize?: number;
length?: (value: V, key: string) => number;
dispose?: (key: string, value: V) => any;
} = {}) {
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 JSON.stringify+Buffer.byteLength to
// approximate the size of what it would take to store. It's certainly
// not 100% accurate, but it should be a fast implementation which
// 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 Buffer.byteLength(JSON.stringify(item), 'utf8');
}

// 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,
dispose,
});
}

Expand Down
7 changes: 6 additions & 1 deletion packages/apollo-server-core/src/ApolloServer.ts
Expand Up @@ -93,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 Down Expand Up @@ -496,13 +500,14 @@ export class ApolloServerBase {
}

private initializeDocumentStore(): void {
this.documentStore = new InMemoryLRUCache({
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,
length: approximateObjectSize,
});
}

Expand Down
5 changes: 4 additions & 1 deletion packages/apollo-server-core/src/__tests__/runQuery.test.ts
Expand Up @@ -573,7 +573,10 @@ describe('runQuery', () => {
approximateObjectSize(parse(querySmall1)) +
approximateObjectSize(parse(querySmall2));

const documentStore = new InMemoryLRUCache<DocumentNode>({ maxSize });
const documentStore = new InMemoryLRUCache<DocumentNode>({
maxSize,
length: approximateObjectSize,
});

await runRequest({ plugins, documentStore, queryString: querySmall1 });
expect(parsingDidStart.mock.calls.length).toBe(1);
Expand Down

0 comments on commit 2a987a9

Please sign in to comment.