はじめに
キャッシュとは、一度取得したデータを一時的に保存しておき、次回同じデータが必要になったときに再取得せずに素早く提供する仕組みです。例えばAPIからデータを取得する場合、最初のリクエスト時にそのデータをキャッシュに保存しておき、次回からはキャッシュからデータを返すようにすると、処理が速くなります。
しかし、初回のデータを取得している間にも次のリクエストが来るかもしれません。ロックを取得して待たせるという選択肢がないTypeScript/JavaScriptの場合、このようなケースを綺麗に扱うのは意外と難しいものです。
その問題を解消するために書いた自作ライブラリの紹介です。
基本的な構成
ObjectIdMapper
JavaScriptのObjectには固有のIDがありません。そのため、Objectに対してロック処理をするためには、Objectを一意に識別する仕組みが必要になります。ObjectIdMapperはWeakMapを用いることで、Objectごとに固有な文字列IDを生成、保持します。
Synchronizer
TypeScript/JavaScriptの世界では、同期化はPromiseを連結していくことで行います。Synchronizerは文字列キーに対応するPromise Chainを保持し、新しいPromiseをその後ろに繋げていくことで同期化を実現します。
ObjectIdMapperと組み合わせることで、オブジェクトごとに独立した同期化を行うことができます。
CachedLazyProvider
Synchronizerを用いると、キャッシュは比較的簡単に実装できます。最大限のパフォーマンスを得るために、キャッシュは3段階で処理を行います。
- 同期化前のキャッシュ取得 (hitA)
- 同期化後のキャッシュ取得 (hitS)
- 同期化後の値生成 (miss)
CachedLazyProviderはその名の通り怠惰で、キャッシュがない状態での初回アクセスはどうしても遅くなります。
CachedProvider
キャッシュがない場合の遅延対策として、キャッシュの有効期限が切れるより前にバックグラウンドでキャッシュを更新することが考えられます。
CachedProviderはCachedLazyProviderの拡張であり、データ取得とは独立して定期的にバックグラウンドでの値更新を行います。適切にTTLを設定することで、値取得時の遅延をほぼ0に抑えることができます。
より詳しい解説
ニーズがあれば書く予定