DataLoader
概要
https://github.com/graphql/dataloader
DataLoader is a generic utility to be used as part of your application's data fetching layer to provide a simplified and consistent API over various remote data sources such as databases or web services via batching and caching.
アプリケーションの データフェッチ層 の一部を担い、効率的なデータの取得を目的としたライブラリ
元は JavaScript のライブラリだが、同じ名前で様々な言語のものが存在する
Go: https://github.com/graph-gophers/dataloader
Batch と Cache という2 つの機能を提供する
インストール
code:sh
$ npm install dataloader
Batching
https://github.com/graphql/dataloader?tab=readme-ov-file#batching
非同期かつ一括で行うデータアクセス
より具体的には、データを取得するためのデータソース(DB)へのアクセスを一定期間待機(Lazy load) し、その間に行われたリクエストをまとめて処理する ための機構
発行される SQL のイメージ
code:sql
SELECT * FROM author
SELECT * FROM post WHERE author id IN (1,2,3})
SELECT * FROM tag WHERE post_id IN (1,2,3,4)
実装するには、Batch Function と呼ばれる関数を定義し、DataLoader コンストラクタに渡す
Batch Function
データ取得の際の key の配列を受け取り、それ対応する value の配列または Error を返す
以下の 2 つのルールを守る必要がある
1. key の配列と value の配列の長さが等しいこと
2. key の配列の順序と value の配列の順序が等しいこと
サンプルコード
code:ts
class PostDataSource {
private loader = new DataLoader(async (keys: number[]) => {
const posts = ...;
const results = posts.filter((post) => keys.includes(post.author_id));
return keys.map(key: number) => results.filter(tag => tag.post_id === key);
});
}
Batch Function への key の登録は loader.load で行う
https://github.com/graphql/dataloader?tab=readme-ov-file#loadkey
load の戻り値は key に対応する value の Promise
複数件の取得を行うには loader.loadMany を用いる
https://github.com/graphql/dataloader?tab=readme-ov-file#loadmanykeys
code:ts
class PostDataSource {
...
async getPostsBy(author_id: name) {
return this.loader.load(author_id)
}
}
Caching
https://github.com/graphql/dataloader?tab=readme-ov-file#caching
Key-Value Store へのデータの メモ化
load に渡した key とそれに対応する value の ペア を Key-Value Store に メモ化 する
これにより、同一 DataLoader インスタンスから実行された Batch 内 で、同一 key を用いて複数回 value を取得するとき、メモ化 された Key-Value Store から値を返す
パフォーマンス向上や DB などのデータソースへの負荷軽減が期待できる
warning.icon DataLoader のインスタンスはリクエストごとに生成すべき
リクエストごとに生成することで、以下を防げる
Cache がメモ化する Key-Value のペアがユーザやコンテキストの異なるリクエストから参照されること
メモリに書き込まれ続け大量のメモリを消化する
Apollo Server の場合、context の dataSource をリクエストごとに初期化する
これにより、DataLoader インスタンスの TTL を単一のリクエストに限定できる
code:ts
import DataLoader from 'dataloader';
interface ContextValue {
dataSources: {
posts: PostsDataSource;
tags: TagsDataSource;
};
}
const resolvers = {
Query: {
authors: () => getAuthors(),
},
Author: {
posts: (parent, _, { dataSources }) => dataSources.posts.getPostsBy(parent.id),
},
Post: {
tags: (parent, _, { dataSources }) => dataSources.tags.getTagsBy(parent.id),
},
};
const schemaWithResolvers = addResolversToSchema({ schema, resolvers });
const apolloServer = new ApolloServer<ContextValue>({ schema: schemaWithResolvers });
const { url } = await startStandaloneServer(apolloServer, {
context: async () => {
return {
dataSources: {
posts: new PostsDataSource(),
tags: new TagsDataSource(),
},
};
},
});
#GraphQL