Configuring Cache Backends
How to configure Apollo Server's cache
Many Apollo Server features take advantage of a cache backend (these features include automatic persisted queries, the response cache plugin, and RESTDataSource
). Apollo Server uses an in-memory cache by default, but you can configure it to use a different backend, such as Redis or Memcached.
You can specify a cache backend by passing a cache
option to the ApolloServer
constructor. Your specified cache backend must implement the KeyValueCache
interface from the @apollo/utils.keyvaluecache
package.
There are many cache backend implementations to choose from, including several implementations provided by Apollo. For example, Apollo maintains an implementation of InMemoryLRUCache
in the @apollo/utils.keyvaluecache
package. Apollo also provides a wrapper class for the keyv
package (which implements several cache backends) named KeyvAdapter
in the @apollo/utils.keyvadapter
package.
Configuring in-memory caching
⚠️ If you are using Apollo Server 3, see the previous version of this article to learn how to protect your cache from denial of service attacks by using a bounded cache. By default, Apollo Server 4's default cache is a bounded in-memory cache backend.
You can configure your server to use a different backend (such as Redis or Memcached) using the cache
constructor option.
If you want to configure the default in-memory cache, Apollo provides the InMemoryLRUCache
class from the @apollo/utils.keyvaluecache
package.
The InMemoryLRUCache
class is a wrapper around the lru-cache
package and has a default maximum of approximately 30MiB of memory. You can configure an instance of InMemoryLRUCache
with the same options as found in the lru-cache
package, see the lru-cache
documentation for more details. You can use Apollo's InMemoryLRUCache
by passing it to the cache
option of the ApolloServer
constructor like so:
1import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache';
2
3const server = new ApolloServer({
4 cache: new InMemoryLRUCache(),
5});
Apollo Server 4 provides the equivalent of the above code snippet out-of-the-box and doesn't require you to install the
@apollo/utils.keyvaluecache
package.
In this example, we've increased the default size and provided a default TTL. For more information on these configuration options, see the lru-cache
documentation.
1import { InMemoryLRUCache } from '@apollo/utils.keyvaluecache';
2
3const server = new ApolloServer({
4 // ...
5 cache: new InMemoryLRUCache({
6 // ~100MiB
7 maxSize: Math.pow(2, 20) * 100,
8 // 5 minutes (in seconds)
9 ttl: 300,
10 }),
11});
Configuring external caching
Apollo no longer maintains any caching backends directly. Instead, we recommend using the keyv
package along with the KeyvAdapter
class provided by the @apollo/utils.keyvadapter
package.
KeyvAdapter
wraps a Keyv
instance and implements the KeyValueCache
interface which is required by Apollo Server. You can use the KeyvAdapter
class to wrap a Keyv
instance and provide it to the cache
option of the ApolloServer
constructor like so:
Install the required packages
1npm install keyv @apollo/utils.keyvadapter
Configure the Apollo Server
cache
1import Keyv from 'keyv';
2import { KeyvAdapter } from '@apollo/utils.keyvadapter';
3
4const server = new ApolloServer({
5 // ...,
6 cache: new KeyvAdapter(new Keyv()),
7});
Implementing your own cache backend
If your requirements are specialized or you'd prefer to implement your own cache backend, you can implement the KeyValueCache
interface and pass it to the ApolloServer
constructor directly.
The KeyValueCache
interface is shown below:
1interface KeyValueCache<V> {
2 get(key: string): Promise<V | undefined>;
3 // ttl is specified in seconds
4 set(key: string, value: V, options?: { ttl?: number | null }): Promise<void>;
5 delete(key: string): Promise<boolean | void>;
6}
Configuring Redis
The @keyv/redis
package uses the ioredis
package under the hood. The second
options argument is passed through to the ioredis.Redis
constructor. See the
ioredis
docs for a list of
available options.
Start by installing the required packages:
1npm install keyv @keyv/redis @apollo/utils.keyvadapter
Single instance
1import Keyv from "keyv";
2import KeyvRedis from "@keyv/redis";
3import { KeyvAdapter } from "@apollo/utils.keyvadapter";
4
5const server = new ApolloServer({
6 typeDefs,
7 resolvers,
8 cache: new KeyvAdapter(new Keyv(new KeyvRedis("redis://user:pass@localhost:6379"))),
9});
Redis Sentinel
1import Keyv from "keyv";
2import { KeyvAdapter } from "@apollo/utils.keyvadapter";
3
4const server = new ApolloServer({
5 typeDefs,
6 resolvers,
7 cache: new KeyvAdapter(new Keyv(
8 new KeyvRedis("redis://user:pass@localhost:6379", {
9 sentinels: [
10 { host: "localhost", port: 26379 },
11 { host: "localhost", port: 26380 },
12 ],
13 })
14 )),
15});
Redis Cluster
The @keyv/redis
package doesn't support ioredis.Cluster
out of the box.
Instead, we can create our own ioredis.Cluster
instance and pass that to
keyv
as the store
object. See the ioredis.Cluster
docs for a list of available options.
Start by installing the packages we'll need:
1npm install keyv @keyv/redis ioredis @apollo/utils.keyvadapter
1import Keyv from "keyv";
2import KeyvRedis from "@keyv/redis";
3import Redis from "ioredis";
4import { KeyvAdapter } from "@apollo/utils.keyvadapter";
5
6const cluster = new Redis.Cluster([
7 { host: "localhost", port: 26379 },
8 { host: "localhost", port: 26380 },
9]);
10
11const server = new ApolloServer({
12 typeDefs,
13 resolvers,
14 cache: new KeyvAdapter(new Keyv({ store: new KeyvRedis(cluster) }), {
15 disableBatchReads: true,
16 }),
17});
Note the
disableBatchReads
option. This disables batching which isn't supported byioredis.Cluster
.
Configuring Memcache
The @keyv/memcache
package uses the memjs
package under the hood. Its second
options argument is passed to memjs.Client.create()
. See the memjs
docs for a list of available options.
Start by installing the required packages:
1npm install keyv @keyv/memcache @apollo/utils.keyvadapter
1import Keyv from "keyv";
2import KeyvMemcache from "@keyv/memcache";
3import { KeyvAdapter } from "@apollo/utils.keyvadapter";
4
5// servers is a comma-separated list of strings
6const servers = [
7 "user:pass@localhost:11211",
8 "user:pass@localhost:11222"
9].join(",");
10
11const memcache = new KeyvMemcache(servers, {
12 retries: 10,
13 expires: 60,
14});
15
16const server = new ApolloServer({
17 typeDefs,
18 resolvers,
19 cache: new KeyvAdapter(new Keyv({ store: memcache })),
20});
Handling cache fetching errors
To provide error tolerance for cache backends that connect via a client (e.g., Redis), we recommend using the ErrorsAreMissesCache
wrapper from @apollo/utils.keyvaluecache. If the cache is unavailable and your request throws an error, ErrorsAreMissesCache
treats that error as a cache miss. At the same time, your cache client can keep trying to reconnect to your cache backend until things are working again, like so:
1import Keyv from "keyv";
2import KeyvRedis from "@keyv/redis";
3import { KeyvAdapter } from "@apollo/utils.keyvadapter";
4import { ErrorsAreMissesCache } from "@apollo/utils.keyvaluecache";
5
6const redisCache = new Keyv(new KeyvRedis("redis://user:pass@localhost:6379"));
7const faultTolerantCache = new ErrorsAreMissesCache(
8 new KeyvAdapter(redisCache),
9);
Legacy caching implementation
Versions of Apollo Server prior to 3.9 use the apollo-server-caching
package to implement caching. The apollo-server-caching
package is no longer maintained, and we do not recommend using it. The KeyValueCache
interface has been moved and is now in the @apollo/utils.keyvaluecache
package.
The InMemoryLRUCache
class has also moved to the @apollo/utils.keyvaluecache
package. The InMemoryLRUCache
class now uses version 7 of lru-cache
, accepting different configuration options and no longer allowing a cache to be unbounded.
The apollo-server-cache-redis
and apollo-server-cache-memcached
packages are no longer receiving updates; we recommend using keyv
instead, as shown above.