使用 DataLoader 缓存数据
缓存是设计可扩展和高性能 GraphQL API 的重要组成部分。对于 GraphQL API,我们可能希望缓存来自慢速外部 API 的字段值。缓存的目标是在最需要的地方提高性能。
缓存是设计可扩展和高性能 GraphQL API 的重要组成部分。
但是,什么是缓存?缓存是一种技术设计,旨在通过将结果临时保存在缓存中来避免计算密集型任务。缓存是重要数据在内存中的一个位置,它可以比常规过程更快地传递给客户端。
例如,对于 GraphQL API,我们可能希望缓存来自慢速外部 API 的字段值。缓存的目标是在最需要的地方提高性能。
缓存如何更快?
对计算密集型数据使用缓存会更快,原因如下:
- 缓存通常不存储在存储中,而是存储在内存中,这要快得多。
- 缓存比数据库小,因此要过滤的值要少得多。
- 虽然某些缓存数据可能会被查询很多次,但它只会从数据库中提取一次,因此多次提取所浪费的时间更少。
我们如何使用缓存?
使用 DataLoader 进行缓存
我首选的缓存平台是 dataloader,在本教程中,我将解释如何将 dataloader 与 Node.js 和 Typescript 一起使用,以便在您的服务器中进行缓存。
dataloader 包是专门为 GraphQL 创建的,但它可以与其他 Node.js 程序一起使用。
使用 DataLoader
1.在你的项目中安装dataloader
通过运行以下命令安装数据加载器:
npm install --save dataloader 或 yarn add dataloader
2.导入数据加载器
首先,导入 dataloader 和 LRU(LRU 是一种缓存算法,它只会将最多 X 个请求的项目放入缓存中)。
import DataLoader from 'dataloader'
import { LRUMap } from 'lru_map'
3. 定义将被保存到我们的缓存的类型
第一种应该是一种表示数据的方式,比如带有名字的字符串,另一种是数据本身。例如,假设我们正在尝试缓存书籍。我们的包名称将是书籍 ID,因此我们可以分辨出哪本书是哪本书。我们的包裹数据将是书籍长度、作者、价格等...
所以在这种情况下,我们的模式看起来像这样:
type book {
id: ID!
name: String!
author: author!
price: Int!
description: String!
length: Int!
}
type author {
id: ID!
name: String!
}
相应的数据类型如下所示:
type packageId = string
type PackageData = /* note that this is the type of the data we represent. */ {
id: string
name: string
author: {
id: string
name: string
}
price: number
description: string
length: number
}
我们编写类型的原因是为了类型安全,假设我们有具有不同值的书籍和玩具,我们不希望以某种方式将玩具插入到我们的书籍缓存中
4.创建数据加载器实例
const dataLoader = new DataLoader<packageId, PackageData>(
async (/* (1) */keys: readonly packageId[]) => { /* (2) */
return await Promise.all(
keys.map((packageId) => {
await getData(packageId).catch((e) => e);
})
);
},{
cacheMap: new LRUMap(100); /* (3) */
}
);
如您所见,我们将用户要求的密钥提供给数据加载器 (1)。
我建议将键设置为某种 id,原因如下:假设在您的 api 中,用户使用其 id 请求数据,因此用户将使用 id:"something" 获取数据,您可以只传递 id作为关键,而不是改变它。
数据加载器将首先检查它是否有缓存中的键,如果有,它将返回数据,而不通过数据库,为您节省大量宝贵的时间。
在那之后(2),我们给数据加载器一个获取数据的函数,以防它在缓存中没有它。在这种情况下,我有函数 getData,并且我正在使用键从我的数据库中“获取数据”。
最后(3)我们给它cacheMap和一些值,该值表示dataloader将缓存多少个查询,在这种情况下,在100个值之后,它将删除最少使用的值(未被查询的那个)最长的时间)为第 101 个值腾出空间。
从现在开始,要查询数据,您只需运行
dataLoader.load(keys)
在 GraphQL 中 使用 Dataloader
Dataloader 旨在与 GraphQL 一起使用,以解决 n+1 问题
N+1 问题是设计 GraphQL API 时的常见问题。查看下面的查询,我们可以看到 GraphQL API 将调用 20 次 Book.author 解析器:
query BooksWithAuthors {
books(first: 20) {
id
title
author {
id
name
}
}
}
根据解析器的实现,此查询可能会触发 20 个 SQL 查询或 API 调用来解析可能写过多本书的作者。 Dataloader 通过缓存、延迟和分组类似的解析器调用来帮助解决这个问题。
如何在 GraphQL 中使用 dataloader 包?
要将数据加载器与 GraphQL 一起使用,只需在上下文中传递它!
现在,您的代码应该看起来像这样:
import DataLoader from "dataloader";
import { LRUMap } from "lru_map";
type packageId = string;
type PackageData = Package;
export type GraphQLContext = {
dataLoader: DataLoader<packageId, PackageData>;
...
};
const dataLoader = new DataLoader<packageId, PackageData>(
async (keys: readonly PackageId[]) => {
return await Promise.all(
keys.map((packageId) => {
await getData(packageId).catch((e) => e);
})
);
},
{
cacheMap: new LRUMap(100),
}
);
...
export async function contextFactory() {
return { dataLoader, ... };
}
现在,在您的解析器中,只需调用 context.dataLoader.load(keys) 就可以了!您现在在您的服务器中有缓存!
解析器实现的示例:
export const resolvers = {
Query: {
getBook: async (parent, input, context) => {
return context.dataLoader.load(input.bookId)
}
}
}