キャッシュアサイドは最もよく使われるキャッシングパターンであり、おそらくその名前を知らずにすでに使用しているパターンです。アプリケーションはまずキャッシュを確認し、ミス時にはデータベースから読み込んで自分でキャッシュにデータを投入します。シンプルで、アプリケーションが制御権を持ち、キャッシュが利用できない場合でも正常に劣化します。これをよく理解すること(2つの微妙なバグを含む)は、このモジュールの他のすべてのキャッシングパターンの基盤となります。
これはRedisマスタークラスの第9部であり、キーの有効期限とTTLに続くキャッシュモジュールの開始です。
パターン
キャッシュアサイド(レイジーローディングとも呼ばれる)は、アプリケーションがキャッシュを直接管理します。読み取り時には:
- Redisでキーを確認します。
- 存在する場合(ヒット)、それを返します。
- 存在しない場合(ミス)、データベースから読み取り、結果をTTL付きでRedisに書き込み、返します。
function getUser(id) {
const cached = await redis.get(`user:${id}`);
if (cached) return JSON.parse(cached); // hit
const user = await db.query('SELECT * FROM users WHERE id = $1', [id]); // miss
await redis.set(`user:${id}`, JSON.stringify(user), 'EX', 300); // populate, 5-min TTL
return user;
}
Enter fullscreen mode Exit fullscreen mode
これがパターン全体です。キャッシュは実際にリクエストされたデータのみで遅延的に埋められるため、誰も読まないものをキャッシュすることはありません。また、回復力があります:Redisがダウンした場合、すべてのリクエストはデータベースにフォールスルーする「ミス」となり、アプリケーションは動作し続けます(遅くなりますが、動作します)。この正常な劣化が、キャッシュアサイドがデフォルトである大きな理由です。
書き込み:更新ではなく無効化
書き込み側では、キャッシュアサイドの標準的なルールは、データベースを更新してからキャッシュエントリを無効化することで、キャッシュの更新ではありません:
function updateUser(id, data) {
await db.query('UPDATE users SET ... WHERE id = $1', [id]);
await redis.del(`user:${id}`); // invalidate; next read re-populates
}
Enter fullscreen mode Exit fullscreen mode
キーを削除することで、次の読み取りがミスとなり、新しいデータベース値から再投入されます。これはキャッシュに新しい値を書き込むよりもシンプルで安全です。新しいものより古い書き込みがキャッシュに残るレースコンディションのクラスを回避できるためです。書き込み時の削除は、キャッシュアサイド読み取りの従来の相方です。
知っておくべき2つのバグ
キャッシュアサイドはシンプルですが、よく知られた2つの問題を引き起こします。
古いキャッシュのレース。 次のことを考えてみてください:リクエストAが読み取り、ミスし、データベースから古い値を読み込みます。Aがそれをキャッシュに書き込む前に、リクエストBがデータベースを更新し、(まだ空の)キャッシュキーを削除します。その後Aが今や古くなった値をキャッシュに書き込み、TTLまで残ります。ウィンドウは小さいですが、負荷がかかると現実的です。主な防御策は適切なTTL(古さが制限されるため)と、正しさが要求される場合には、より慎重なパターン(後で取り上げます)です。ほとんどのキャッシングでは、短いTTLで十分です。間違った値はすぐに自己修正されるためです。
サンダリング herd(キャッシュスタンピード)。 人気のあるキーが期限切れになると、多くの同時リクエストがすべてミスし、すべてデータベースに同時にヒットして再投入し、キーが最もホットな時に負荷が急増します。これは十分に一般的で、このモジュールの後半で独自の記事に値します。迅速な緩和策は、TTLにジッターを追加してキーが同期して期限切れにならないようにすることと、ロックを使用して1つのリクエストのみがキーを再構築し、他のリクエストは待機するようにすることです。
キャッシュするものと期間の選択
2つの決定がキャッシュアサイドのセットアップを形成します。キャッシュするもの:読み取りが書き込みよりはるかに多く、取得にコストがかかるデータ(テーブルを横断して結合されたユーザープロファイル、計算されたダッシュボード、めったに変更されない設定)。読み取りごとに変更されるデータや、取得が簡単なデータをキャッシュすると、利点なしに複雑さが増します。
期間(TTL):トレードオフは新鮮さとヒット率です。短いTTLはデータを新鮮に保ちますが、より頻繁にミスします。長いTTLはより良くキャッシュしますが、より古いデータを返します。データがどの程度古くても許容できるかに合わせてください:高速に変更されるデータには秒単位、安定したデータには分または時間単位。正確な新鮮さが重要な場合は、長いTTLと書き込み時の明示的な無効化を組み合わせて、変更時にキャッシュがすぐに更新され、TTLは単なるバックストップとなるようにします。
キャッシュアサイドがデフォルトである理由
キャッシュアサイドはシンプルさと回復力によって勝利します。アプリケーションがロジックを所有するため、設定する特別なキャッシュインフラストラクチャはありません。使用されるもののみをキャッシュします。キャッシュの停止時にはデータベースにフォールスルーすることで生き残ります。そして、他のパターンと組み合わせることができます:リードスルー、ライトスルーなどは、本質的にキャッシュロジックを別のレイヤーに移動したり、より洗練されたものにしたキャッシュアサイドです。
内面化すべきパターン:キャッシュを確認し、ミス時には読み込んで投入し、書き込み時には無効化する。古さのバックストップとしてTTLを追加し、古い書き込みのレースとスタンピードに注意してください。これで実際のキャッシングの大部分をカバーし、このモジュールのすべてがこれに基づいて構築されます。
次に、キャッシュロジックをアプリケーションからキャッシュレイヤー自体に移動するリードスルーとライトスルーキャッシングと、それがトレードオフをどのように変えるかを見ていきます。
重要なポイント
- キャッシュアサイド:まずRedisを確認し、ミス時にはデータベースから読み込み、TTL付きでキャッシュに投入して返します。
- 遅延的に埋められ(リクエストされたデータのみがキャッシュされる)、正常に劣化します。キャッシュの停止は単にデータベースにヒットするミスになります。
- 書き込み時には、データベースを更新してキャッシュキーを削除し、次の読み取りで再投入するようにします。新しい値をキャッシュに書き込むのではなく。
- 2つのバグに注意:古い書き込みのレース(TTLで制限される)と、ホットキーが期限切れになった時のキャッシュスタンピード。
- 読み取りが多く取得にコストがかかるデータをキャッシュし、データが安全にどの程度古くても許容できるかに基づいてTTLを設定します。
よくある質問
キャッシュアサイドパターンとは何ですか?
アプリケーションがまずキャッシュを確認し、ミス時にはデータベースからデータを読み込み、TTL付きでキャッシュに保存して返すキャッシングアプローチ。アプリケーションがキャッシュを直接管理し、リクエストされたデータのみで遅延的に埋めます。
書き込み時にキャッシュを更新または削除すべきですか?
キャッシュキーを削除(無効化)して、次の読み取りでデータベースから再投入させます。削除は、古い値が新しいものより後にキャッシュに書き込まれるレースを回避します。キャッシュを直接更新すると発生する可能性があります。
キャッシュアサイドでRedisがダウンしたらどうなりますか?
アプリケーションは動作し続けます。すべての読み取りがデータベースにフォールスルーするキャッシュミスになり、レスポンスは遅くなりますが正しいです。この正常な劣化はキャッシュアサイドの主要な利点です。
キャッシュスタンピードとは何ですか?
人気のあるキャッシュキーが期限切れになり、多くの同時リクエストがすべてミスしてデータベースにヒットし、再構築して負荷が急増すること。TTLにジッターを追加してキーが一緒に期限切れにならないようにし、ロックを使用して1つのリクエストのみがキーを再構築するようにすることで緩和します。
キャッシュTTLの選び方は?
データがどの程度古くても許容できるかに基づいて、新鮮さとヒット率のバランスを取ります:高速に変更されるデータには短いTTL、安定したデータには長いTTL。正確な新鮮さが重要な場合は、長いTTLと書き込み時の明示的な無効化を使用して、変更時にキャッシュがすぐに更新されるようにします。
さらに読む
この記事は元々amanksingh.com/blog/redis-cache-asideで公開されました。
著者について
Aman Kumar Singhはインドのノイダを拠点とするチームリーダー兼シニアソフトウェアエンジニアで、TypeScript、Next.js、NestJS、PostgreSQL、Redisを使用したフルスタックエンジニアリング、システム設計、本番SaaSについて執筆しています。
- ポートフォリオ: amanksingh.com
- GitHub: amansingh1501
- LinkedIn: amansingh1597
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.