caching について
読み取りの多いシステムで、アプリケーション層や分散キャッシュを設計するためのスキルです。戦略の選定、新鮮度と削除方針、容量見積もり、キャッシュ障害への備えを扱います。
主な機能
- 01キャッシュ配置と読み取り方式
- 02書き込み方式と削除ポリシーの選定
- 03データ鮮度と無効化の計画
- 04スタンピードとホットキーの対策
- 05容量見積もりと監視
活用シーン
- 01データストアへの反復読み取りの削減
- 02鮮度要件に応じたキャッシュ動作の選定
- 03キャッシュ再起動時のオリジン保護
- 04ホットデータセット向けの容量計画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ proyecto26/ system-design-skills --skill cachingインストール後、Claude Code で/cachingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/deep-dive.mdreferences/providers/aws.mdazure.mdgcp.mdgeneric.md詳細
キャッシュ
頻繁に使われるデータのコピーを読み取り元の近くに置くことで、ほとんどのリクエストで低速な経路を使わずに済みます。 キャッシュは読み取りが多いシステムに最も効果的な施策であり、同時に、気づきにくい間違いが起こりやすい施策でもあります。キャッシュを追加すると、古いデータや誤ったデータを返す第2の情報源が生まれ、動作に問題が生じた際には障害を増幅することもあるためです。
導入を検討するタイミング
読み取りが大半を占める(back-of-the-envelopeで読み取りと書き込みの比率が高い);同じデータが繰り返し読み取られる;データストアが読み取りのボトルネックになっている;または再計算のコストが高い場合です。キャッシュを使うと、読み取りのレイテンシを短縮し、オリジンの負荷を軽減できます。
導入すべきでない場合
書き込みが多いデータ、または一度しか読み取られないデータ(ヒット率が低く、純粋なオーバーヘッドになります)。常に最新である必要があり、古さが一切許容されないデータ(キャッシュは本質的に古いコピーです。厳密な鮮度が求められる場合は、ソースを参照するか、consistency-coordinationを使用してください)。読み取りが問題だと数値で確認する前にキャッシュを追加しないでください(YAGNI)。新たな障害要因が増え、運用対象ももう1つ増えることになります。
まず確認すること
- 読み取りと書き込みの比率、ヒット率 — ワーキングセットはキャッシュ可能ですか?(→
back-of-the-envelope、80/20。) - 古さの許容範囲 — 数秒ですか? 数分ですか? 読み取り時に自身の書き込み結果を参照できる必要がありますか?
- ワーキングセットのサイズ — キャッシュノード全体のRAMにホットセットが収まりますか?
- 書き込み時の整合性 — キャッシュとストアの間に一時的な不一致があっても問題ありませんか?
- 退避の契機 — アクセスパターンはどのようなものですか(新しさ? 頻度? 時間制限?)。
選択肢
キャッシュする場所(多くの場合は多層構成です):クライアント/ブラウザー → CDNエッジ(→ content-delivery)
→ アプリケーション/プロセス内 → 分散キャッシュ(Redis/Memcached)→ データベースのバッファ
プール。このスキルでは、アプリケーション層と分散層に焦点を当てます。
読み取り戦略
- キャッシュアサイド(遅延読み込み): アプリがキャッシュを確認し、ミスした場合はストアを読み取ってキャッシュに格納します。 読み取りパターンが予測できない場合に使います。ほとんどのシステムでのデフォルトです。
- リードスルー: ミスした場合に、キャッシュライブラリがストアからデータを取得します。アプリのコードをシンプルに保ち、キャッシュポリシーを一元管理する場合に使います。
書き込み戦略
- ライトスルー: キャッシュとストアに同期して書き込みます。書き込み直後の読み取りで最新データを参照する必要があり、書き込みが遅くなっても許容できる場合に使います。
- ライトバック(ライトビハインド): まずキャッシュに書き込み、非同期でストアに反映します。小さなデータ損失の可能性が許容される、書き込みが多い、または集中する経路に使います。
- ライトアラウンド: ストアにのみ書き込み、読み取り時にキャッシュへ格納します。書き込んだデータがすぐ再読み込みされることがまれな場合に使います(キャッシュの入れ替わりを防ぎます)。
退避ポリシー
- LRUは新しさに偏ったアクセス向け(最も一般的)、LFUは人気が安定しているデータ向け、TTLは古さを制限するため、FIFOは使用頻度が低い方式です。アクセスパターンに合ったポリシーを選んでください。
トレードオフ
| 選択肢 | 解決できること | 悪化すること | 変更するタイミング |
|---|---|---|---|
| キャッシュアサイド | シンプルで堅牢(キャッシュ停止時も遅くなるだけ) | キーごとの最初の読み取りはミスする;書き込み後に古いデータを返すリスクがある | ミスのコストが高すぎる → リードスルー+ウォームアップ |
| リードスルー | 一元管理でき、アプリのコードがすっきりする | アプリがキャッシュライブラリに依存する;コールドスタート時にミスが発生する | キーごとに独自の読み込みロジックが必要 |
| ライトスルー | 書き込み後の読み取りで最新データを返す | 書き込みが遅くなる;読み取られない可能性のあるデータもキャッシュされる | 書き込みが大半を占め、再読み込みされない → ライトアラウンド/ライトバック |
| ライトバック | 高速で、書き込みの集中を吸収する | クラッシュ時にデータ損失の可能性がある;複雑 | 直近の書き込みの永続性が必要 |
| ライトアラウンド | 書き込み専用データによるキャッシュの入れ替わりを防ぐ | 書き込み直後のキーは、最初の読み取りでミスする | そのデータが書き込み直後に読み取られる → ライトスルー |
| TTLによる退避 | 古さを自動的に制限する | 大量の同時期限切れによってオリジンへのアクセスが集中する可能性がある | ジッターを追加する/ソフトTTLで更新する |
ストレス時の動作
キャッシュの動作が不安定になると、単に役に立たなくなるだけではなく、オリジンを停止させることがあります。
- サンダリングハード/スタンピード: ホットキーの有効期限が切れる(またはキャッシュが再起動する)と、何千もの同時キャッシュミスが一度にストアへ到達します。対策: キーごとのロック/リクエストの集約(シングルフライト)、早期または確率的なリフレッシュ、TTL のジッター。
- キャッシュペネトレーション: 存在しないキーへのリクエストが毎回キャッシュを迂回します(悪意による場合もよくあります)。対策: 否定結果をキャッシュする(短い TTL)、または前段にブルームフィルターを置きます。
- ホットキー: 1 つのキー(有名人、バイラルコンテンツなど)が単一ノードのスループットを超えます。対策: キーを複数ノードに複製する、ローカル/L1 層を追加する、または値をシャード化します。
- 大量エビクション/コールドキャッシュ: フラッシュまたはデプロイの後にヒット率が急落し、オリジンに全負荷がかかります。対策: 重要なキーを事前にウォームアップし、トラフィックを段階的に増やします。
- 書き込み後の陳腐化: ストアは変更されたのに、キャッシュが更新されていません。対策: 書き込み時に無効化する、ライトスルーを使う、または TTL を短くします。許容する陳腐化の範囲に応じて選択してください。
監視項目: ヒット率、p99 レイテンシ、エビクション率、キーの分布(ホットスポット)、キャッシュ再起動中のオリジン QPS。
適用方法
- 入力条件を明確にする — 読み取りと書き込みの比率、許容する陳腐化の範囲、ホットセットのサイズを確認します(まず確認することを参照)。読み取りがボトルネックだと示す数値がまだない場合は、そこで止めてください。まだキャッシュは不要です(→
back-of-the-envelope)。 - トレードオフ表から戦略を選ぶ — 読み取り戦略(デフォルトはキャッシュアサイド)、許容する陳腐化の範囲に応じた書き込み戦略、アクセスパターンに合ったエビクションポリシーを選びます。
- 主要な設定値を決める — キーの命名規則、TTL(ジッターを含む)、キーのファミリーごとの無効化イベントを定義します。否定キャッシュとシングルフライトを導入前に決めておき、最初のインシデント後に検討することがないようにします。
- 選択した構成をストレステストする — ストレス時の動作に挙げた各障害(スタンピード、ペネトレーション、ホットキー、コールドキャッシュ、書き込み後の陳腐化)を確認し、トラフィック特性によって起こり得るものに対策が用意されていることを確かめます。
- 数値に基づいて容量を決める — ノード全体でホットセットが RAM に収まるようにし、目標ヒット率(90% 以上)を妥当性確認して、ノード数がピーク QPS に対応できることを確かめます(→ 重要な数値)。
- プロバイダーを選ぶ — まず汎用のレシピを使い、ユーザーがクラウドを指定した場合に限り、プロバイダーファイルを開きます(プロバイダーの選択を参照)。
- 回答でトレードオフを示す —
Decision | Solves | Worsens | Change it when…という表を用意し、ここで選んだ項目ごとに 1 行記載します(例:オプション、そのストア、障害時のポリシー)。最後の列には具体的な変更のきっかけ(数値または観測可能なシグナル)を記載します。そのうえで、最初に破綻する箇所を示します。
すべきこと/すべきでないこと
すべきこと
- デフォルトでキャッシュアサイドを使います。キャッシュが停止しても、遅くなるだけで正しさは保たれます。
- すべてのエントリーに TTL を設定し、キーの有効期限が一斉に切れないようジッターを加えます。
- ホットキーには、リリース前にシングルフライト/リクエストの集約を追加します。
- 許容する陳腐化の範囲を明示して、書き込み時に無効化する(またはライトスルーを使う、TTL を短くする)ようにします。
- ホットセットに合ったサイズのキャッシュを用意し、ヒット率、エビクション、オリジン QPS にアラートを設定します。
すべきでないこと
- 数値によって読み取りがボトルネックだと証明される前に、キャッシュを追加しないでください(YAGNI)。
- 常に最新でなければならないデータをキャッシュしないでください。代わりにデータソースを参照します。
- 大量の有効期限切れやコールドスタートによってオリジンに全負荷をかけないでください(ウォームアップし、段階的に増やします)。
- 存在しないキーへの大量リクエストを無視しないでください。否定結果をキャッシュするか、前段にブルームフィルターを置きます。
- スキーマのバージョンをまたいで同じキーを再利用しないでください。代わりにバージョンを上げます(
...:v2)。
重要な数値
キャッシュノードは約 100k~1M QPS を処理でき、RDBMS(約 1k)を大きく上回ります。メモリアクセスは約 100 ns、ディスクアクセスはミリ秒単位です。これがキャッシュが有効な理由です。ホットセットに合わせてキャッシュのサイズを決めます(データの約 20% が読み取りの約 80% を占めます)。目標ヒット率は通常 90% 以上です。それを下回る場合は、データがキャッシュに適しているかを問い直してください。→ back-of-the-envelope。
インターフェースの概要
キャッシュエントリは契約です。キー(安定しており、名前空間で区別されます。例:user:123:profile)、値(シリアライズ済み。サイズに注意してください)、TTLで構成されます。キーの種類ごとに無効化イベント(書き込み時か、TTL 経過時か、バージョン更新時か)を決めてください。バージョン付きキー(...:v2)を使えば、削除競合を起こさず、新しいキーを書き込むことで無効化できます。
プロバイダーの選択
前述の汎用的なレシピ(Redis または Memcached、セルフホストまたはマネージド)をデフォルトとして使ってください。ユーザーがクラウドを指定した場合は、マネージドサービスとの対応関係、制限、プロバイダー固有のトレードオフについて、references/providers/<provider>.md を確認してください。そのプロバイダー向けのファイルがない場合は、汎用的なレシピを回答とします。
図
キャッシュアサイドの読み取りパス(アプリケーション → キャッシュ → ミス → オリジン → 格納)や、スタンピード/フォールバックのフローを可視化するには、プラグイン内の architecture-diagram スキルを使用してください。キャッシュノードにはキャッシュの色、オリジンにはストアの色を使い、ミスの経路には破線の矢印を使います。
関連する構成要素
content-delivery— これと組み合わせて使います。この上位のエッジレイヤーであり、静的コンテンツやメディアの CDN/エッジキャッシュを扱います(その概念はこちらが担当します)。data-storage— キャッシュが保護するオリジンとして、これに依存します。読み取りレプリカは読み取りのキャッシュの代替手段であり、キャッシュノードのシャーディングはそのパーティショニングを反映します。(コンシステントハッシュの理論はconsistency-coordinationが担当します。)consistency-coordination— 古いデータを許容できず、読み取り後の書き込み反映やより強い保証が必要な場合に、これと組み合わせて使います。back-of-the-envelope— このスキルに情報を提供します。キャッシュの導入を正当化する読み取り比率とホットセットのサイズを算出します。resilience-failure— これと組み合わせて使います。そこで扱うレート制限とサーキットブレーカーは、動作不良のキャッシュが引き起こす再試行の嵐を抑えるのに役立ちます。system-design— オーケストレーターが担当する概念です。推論ループ、トレードオフの検討方法、10 の障害モードを扱います。
参考資料
references/deep-dive.md— 無効化パターン、シングルフライト/リクエストの集約、キャッシュのシャーディングにおけるコンシステントハッシュ、Redis と Memcached の内部構造、ローカル(L1)と分散(L2)の階層について説明します。キャッシュレイヤーを詳細に設計する際に読んでください。references/providers/{generic,aws,azure,gcp}.md— 環境ごとのサービス対応関係、制限、注意点。