6

The docs (https://www.apollographql.com/docs/apollo-server/features/data-sources.html#Using-Memcached-Redis-as-a-cache-storage-backend) show code like this:

const { RedisCache } = require('apollo-server-cache-redis');

const server = new ApolloServer({
  typeDefs,
  resolvers,
  cache: new RedisCache({
    host: 'redis-server',
    // Options are passed through to the Redis client
  }),
  dataSources: () => ({
    moviesAPI: new MoviesAPI(),
  }),
});

I was wondering how that cache key is used, considering it seems like the caching is actually custom implemented in something like MoviesAPI() and then used via context.dataSources.moviesAPI.someFunc(). For example, say I wanted to implement my own cache for a SQL database. It'd look like

  cache: new RedisCache({
    host: 'redis-server',
  }),
  dataSources: () => ({
    SQL: new SQLCache(),
  }),
});

where SQLCache has my own function that connects to the RedisCache like:

  getCached(id, query, ttl) {
    const cacheKey = `sqlcache:${id}`;

    return redisCache.get(cacheKey).then(entry => {
      if (entry) {
        console.log('CACHE HIT!');
        return Promise.resolve(JSON.parse(entry));
      }
      console.log('CACHE MISS!');
      return query.then(rows => {
        if (rows) redisCache.set(cacheKey, JSON.stringify(rows), ttl);
        return Promise.resolve(rows);
      });
    });
  }

So that means I have RedisCache in both the ApolloServer cache key and dataSource implementation. Clearly, the RedisCache is used in the dataSource implementation, but then what does that ApolloServer cache key do exactly?

Also on the client, examples mostly show use of InMemoryCache instead of Redis cache. Should the client Apollo cache be a different cache from the server cache or should the same cache like RedisCache be in both places?

atkayla
  • 8,143
  • 17
  • 72
  • 132

1 Answers1

9

The cache passed to the ApolloServer is, to my knowledge, strictly used in the context of a RESTDataSource. When fetching resources from the REST endpoint, the server will examine the Cache-Control header on the response, and if one exists, will cache the resource appropriately. That means if the header is max-age=86400, the response will be cached with a TTL of 24 hours, and until the cache entry expires, it will be used instead of calling the same REST url.

This is different than the caching mechanism you've implemented, since your code caches the response from the database. Their intent is the same, but they work with different resources. The only way your code would effectively duplicate what ApolloServer's cache already does is if you had written a similar DataSource for a REST endpoint instead.

While both of these caches reduce the time it takes to process your GraphQL response (fetching from cache is noticeably faster than from the database), client-side caching reduces the number of requests that have to be made to your server. Most notably, the InMemoryCache lets you reuse one query across different places in your site (like different components in React) while only fetching the query once.

Because the client-side cache is normalized, it also means if a resource is already cached when fetched through one query, you can potentially avoid refetching it when it's requested with another query. For example, if you fetch a list of Users with one query and then fetch a user with another query, your client can be configured to look for the user in the cache instead of making the second query.

It's important to note that while resources cached server-side typically have a TTL, the InMemoryCache does not. Instead, it uses "fetch policies" to determine the behavior of individual queries. This lets you, for example, have a query that always fetches from the server, regardless of what's in the cache.

Hopefully that helps to illustrate that both server-side and client-side caching are useful but in very different ways.

Daniel Rearden
  • 80,636
  • 11
  • 185
  • 183
  • 1
    Thank you for the crystal clear explanation! Taking a closer look at `MoviesAPI`, it actually doesn't implement custom caching like my `SQLCache`, but seems to use helper methods from extending `RESTDataSource`, so it makes sense like you said, that it knows to use `cache` passed into `ApolloServer`. In that case, I also feel like `cache` should really be called `restCache` to avoid confusion haha, but oh well. Indeed, I have seen the benefits of the instant client-side cache, but thanks to your help, I now understand the diff and purpose from server-side cache a whole lot better now! Cheers! – atkayla Nov 18 '18 at 16:50
  • 1
    I agree, it's a bit confusing and not well documented. I believe `apollo-datasource`, like `apollo-link-state` are still in active development. So, FWIW, those APIs are likely to change in the future. – Daniel Rearden Nov 18 '18 at 16:54
  • 1
    Hey it's been a while! So with more features comes more confusion. This seems like a level past the `RESTDataSource` cache and a little closer to what I originally wanted: https://www.apollographql.com/docs/apollo-server/features/caching/#saving-full-responses-to-a-cache, but still not quite the same as "caching the response from the database"? Can you comment on the new functionality? :P I think it might work because my resolvers just do a database call with a cache in front of them, so it seems like this would allow me to put that cache at the whole server level instead of on each resolver? – atkayla Jul 10 '19 at 18:07
  • If you need to override the upstream TTL value, you can do that via following method in your `RESTDataSource`: `willSendRequest(request: RequestOptions) { request.cacheOptions = { ttl: 3600 // 1h } } ` – Andi Jul 23 '20 at 12:13