Skip to content

Commit

Permalink
Implement simple UnboundedCache (#6535)
Browse files Browse the repository at this point in the history
This introduces a simple Map-backed, unbounded, in-memory cache
which implements TTLs. This lets us remove the dependency on
keyv and @apollo/utils.keyvadapter for a thing that we're going
to actively tell people not to use anyway.
  • Loading branch information
trevor-scheer committed Jun 15, 2022
1 parent 29bb2f7 commit 67d9036
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 130 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ The version headers in this history reflect the versions of Apollo Server itself
## vNEXT

- Remove internal dependency on `apollo-server-caching`, switch over to `@apollo/utils.keyvaluecache`. This PR specifically also introduces Keyv as an unbounded cache solution, but will replace with our own simple implementation in a follow-up PR targeting this minor version release. [PR #6522](https://github.com/apollographql/apollo-server/pull/6522)
- Remove dependency on `keyv`/`@apollo/utils.keyvadapter` in favor of a simple `Map`-backed cache which implements TTL [PR #6535](https://github.com/apollographql/apollo-server/pull/6535)

## v3.8.2

Expand Down
123 changes: 0 additions & 123 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 0 additions & 2 deletions packages/apollo-server-core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
"node": ">=12.0"
},
"dependencies": {
"@apollo/utils.keyvadapter": "^1.0.1",
"@apollo/utils.keyvaluecache": "^1.0.1",
"@apollo/utils.logger": "^1.0.0",
"@apollo/utils.usagereporting": "^1.0.0",
Expand All @@ -43,7 +42,6 @@
"async-retry": "^1.2.1",
"fast-json-stable-stringify": "^2.1.0",
"graphql-tag": "^2.11.0",
"keyv": "^4.3.0",
"loglevel": "^1.6.8",
"lru-cache": "^6.0.0",
"sha.js": "^2.4.11",
Expand Down
5 changes: 2 additions & 3 deletions packages/apollo-server-core/src/ApolloServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ import { InternalPluginId, pluginIsInternal } from './internalPlugin';
import { newCachePolicy } from './cachePolicy';
import { GatewayIsTooOldError, SchemaManager } from './utils/schemaManager';
import * as uuid from 'uuid';
import { KeyvAdapter } from '@apollo/utils.keyvadapter';
import Keyv from 'keyv';
import { UnboundedCache } from './utils/UnboundedCache';

const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldDefinitionNode) {
Expand Down Expand Up @@ -711,7 +710,7 @@ export class ApolloServerBase<
// random prefix each time we get a new schema.
documentStore:
this.config.documentStore === undefined
? new KeyvAdapter(new Keyv())
? new UnboundedCache()
: this.config.documentStore === null
? null
: new PrefixingKeyValueCache(
Expand Down
35 changes: 35 additions & 0 deletions packages/apollo-server-core/src/__tests__/UnboundedCache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { UnboundedCache } from '../utils/UnboundedCache';

describe('UnboundedCache', () => {
beforeAll(() => {
jest.useFakeTimers();
});
afterAll(() => {
jest.useRealTimers();
});

it('basic get, set, delete', async () => {
const cache = new UnboundedCache();

await cache.set('key', 'value');
expect(await cache.get('key')).toBe('value');

await cache.delete('key');
expect(await cache.get('key')).toBeUndefined();
});

it('get with ttl', async () => {
const cache = new UnboundedCache();

// 1s, or 1000ms
await cache.set('key', 'value', { ttl: 1 });

// check that it's there at 999ms
jest.advanceTimersByTime(999);
expect(await cache.get('key')).toBe('value');

// expire
jest.advanceTimersByTime(1);
expect(await cache.get('key')).toBeUndefined();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import gql from 'graphql-tag';
import type { DocumentNode } from 'graphql';

import { ApolloServerBase } from '../ApolloServer';
import { KeyvAdapter } from '@apollo/utils.keyvadapter';
import assert from 'assert';
import { UnboundedCache } from '../utils/UnboundedCache';

const typeDefs = gql`
type Query {
Expand Down Expand Up @@ -54,7 +54,7 @@ describe('ApolloServerBase documentStore', () => {
const options = await server.graphQLServerOptions();
const embeddedStore = options.documentStore;
assert(embeddedStore);
expect(embeddedStore).toBeInstanceOf(KeyvAdapter);
expect(embeddedStore).toBeInstanceOf(UnboundedCache);

await server.executeOperation(operations.simple.op);

Expand Down
35 changes: 35 additions & 0 deletions packages/apollo-server-core/src/utils/UnboundedCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import type { KeyValueCache } from '@apollo/utils.keyvaluecache';

export class UnboundedCache<T = string> implements KeyValueCache<T> {
constructor(
private cache: Map<
string,
{ value: T; deadline: number | null }
> = new Map(),
) {}

async get(key: string) {
const entry = this.cache.get(key);
if (!entry) return undefined;
if (entry.deadline && entry.deadline <= Date.now()) {
await this.delete(key);
return undefined;
}
return entry.value;
}

async set(
key: string,
value: T,
{ ttl }: { ttl: number | null } = { ttl: null },
) {
this.cache.set(key, {
value,
deadline: ttl ? Date.now() + ttl * 1000 : null,
});
}

async delete(key: string) {
this.cache.delete(key);
}
}

0 comments on commit 67d9036

Please sign in to comment.