与 GraphQL 数据加载器一起实现内存缓存

Posted

技术标签:

【中文标题】与 GraphQL 数据加载器一起实现内存缓存【英文标题】:Implementing In-memory cache alongside GraphQL Dataloaders 【发布时间】:2020-11-15 01:08:28 【问题描述】:

我正在考虑向我的应用程序添加内存缓存(如 redis),但在理解它们如何组合在一起时遇到了一些问题。

const postLoader = new DataLoader(async keys => 
    // ... sql code to get multiple posts by ids
)

const postRepository = 
    async get(id) 
        let post = await cachingImplementation.get("post:" + id)

        if (!post) 
            post = await postLoader.load(id)
        

        return post
    

我了解对数据库进行批量查询的需要,但同样的原则是否适用于对 redis 服务器的查询?

在这种情况下,如果我在同一个时钟周期内运行 postRepository.get 方法 10 次,我将不得不向 redis 服务器发出 10 个不同的请求。

这是个问题吗?我是否应该将实际的获取源(缓存或数据库)移动到数据加载器解析器中,这样它就不会直接执行 sql 代码,而是先查看缓存,然后再查看数据库。

例如

cache = 
  1: ...,
  2: ...

如果我要求 id 为 1、2、3 的帖子,缓存中只有两个。所以我必须过滤掉现有的并在数据库中查询剩余的,或者只检查请求的 id 是否与返回的行长度匹配,如果不匹配,则查询数据库中的所有内容。

这两种方法的缺点是什么?有首选的解决方案吗?

【问题讨论】:

【参考方案1】:
    向任何数据库发出多个请求与将这些请求聚合为一个的主要成本是网络。使用内存数据库并不能消除此成本。所以我建议你使用数据加载器内部的缓存。 逐步填充缓存是可行的方法,但不要忘记为 Redis 键设置过期日期,因为如果缓存每个实体,您可能很快就会耗尽内存。

根据您的应用程序,API 层缓存可能是您更好的选择。如果您使用的是 GraphQL,请查看 Apollo Server Caching Doc。

在我们的系统中,我们称之为数据加载器,API 聚合层。您要在这里实现的是拥有一个 API 聚合层缓存。我建议无论数据模型如何,都将这个缓存泛化,并在想要缓存数据加载器时使用高阶函数。

const memo = (type, loadData) => 
  return async (keys) => 
     cacheData, notFoundKeys  = loadFromRedis(type, keys);
    let data = cacheData;
    if (notFoundKeys.length > 0) 
      loadedData = await loadData(notFoundKeys);
      populateCache(type, notFoundKeys, data);
      data = addLoadedData(cacheData, loadedData);
    
    return data;
  


const postLoaderMemoized = memo('post', async keys => 
  // ... sql code to get multiple posts by ids
)

const postLoader = new DataLoader(postLoaderMemoized)

【讨论】:

我确实浏览了 Apollo 文档进行缓存,但它们似乎没有提供除超时之外的过期机制。我正在开发的应用程序允许用户创建帖子和其他实体并对其进行编辑,因此基于时间的到期并不适合,至少乍一看不是。我有必要手动使缓存因某些突变而无效。他们是否碰巧为此提供了我错过的 API? 所以 Redis (data-loader) 缓存对你来说可能是一个不错的选择(我上面的解决方案是 Redis 缓存)。我刚刚提到了 Apollo 缓存,因为它有时更适合某些应用程序。我仍然认为除了在你的突变中缓存失效之外,你还需要使用 Redis 过期。除非您的帖子数量有限,否则将所有查看过的帖子存储在 Redis 上可能很快就会填满您机器的内存。 是的,我将使用过期超时,但它们通常会比我认为的 Apollo 缓存更长。您是否碰巧知道任何可以将“关注点”分开一点的模式,因为我不是特别喜欢将缓存查询与数据库查询混合在一起,它似乎太紧密地耦合在一起了。 我们在数据访问层发出数据库请求,我们的平台(应用)层函数然后调用数据层访问数据库,最后,我们从API聚合层(数据加载器)调用平台层函数,最后API层调用数据加载器(有时直接调用平台层)。您可以在任何这些层之前放置一个 Redis 缓存,但我喜欢使用高阶函数(在您的情况下)的 API 聚合层缓存。如果您使用 SQL 并且需要更有条理的数据访问层,我还建议您查看prisma。

以上是关于与 GraphQL 数据加载器一起实现内存缓存的主要内容,如果未能解决你的问题,请参考以下文章

跨上下文创建的数据加载器缓存(graphQl ApolloServer)

如何将 Realm 与 Relay/GraphQL 一起使用

在 TypeORM 与 GraphQL 的多对多关系上使用数据加载器,查询多对多

Glide图片加载的用法介绍和三级缓存实现

Android内存缓存管理LruCache源码解析与示例

将朴素贝叶斯分类器保存在内存中