アーキテクチャ●●○○○

キャッシュ

一度取得・計算した結果を再利用のために保存する仕組み。速度改善の常套手段。

概要

キャッシュとは、一度取得したり計算したりした結果を手元に取っておき、次に同じものが要求されたら保存済みの結果を返す仕組みです。時間のかかる処理を毎回やり直す代わりに「前回の答えを使い回す」ことで、応答速度を上げ、元の資源(サーバ やデータベース)の負荷を減らします。

この考え方はシステムのあらゆる層に現れます。CPU の内部にはメモリより高速なキャッシュがあり、ブラウザは一度ダウンロードした画像や CSS をディスクに保存し、CDN は世界中の拠点にコンテンツの複製を置き、アプリケーションは重い SQL の結果をメモリに保持します。「遅い場所へのアクセスを、速い場所の複製で肩代わりする」という一つの原理が、規模を変えて何重にも重なっているのです。

要求されたデータがキャッシュに存在することを「ヒット」、存在せず本来の取得先まで取りに行くことを「ミス」と呼びます。ヒット率をいかに上げるかがキャッシュ設計の中心的な関心事です。

なぜ生まれたか

コンピュータの世界には「速い記憶装置は小さく高価で、大きく安価な記憶装置は遅い」という宿命的なトレードオフがあります。CPU の演算速度に対してメモリは遅く、メモリに対してディスクはさらに遅く、ネットワーク越しのアクセスはその何桁も遅い。すべてを速い装置に置くことは経済的に不可能です。

一方で、アクセスには強い偏りがあります。直前に使ったデータは近いうちにまた使われやすく(時間的局所性)、人気のあるページには要求が集中します。ならば「よく使われる一部だけを速い場所に複製しておけば、大部分の要求は速い場所で捌ける」——この観察がキャッシュの原点です。全体を速くする代わりに、よく使う部分だけを速くするという発想で、コストと速度の矛盾を実用的に解消したのです。

詳細

ヒットとミスの流れ

キャッシュを挟んだ読み取りの基本形は「まずキャッシュを見る → あれば返す(ヒット)→ なければ本来の取得先から取り、キャッシュに保存してから返す(ミス)」という分岐です。この方式はキャッシュアサイド(cache-aside)と呼ばれ、アプリケーションキャッシュの最も一般的なパターンです。

クライアントキャッシュ複製を保持DB本来の取得先① 要求② ミスヒット: 保存済みの結果を即返す④ 結果をクライアントへ返す③ キャッシュに保存次回以降はヒットする
キャッシュアサイドの読み取り — ヒットは手前のキャッシュで折り返し、ミスだけが奥のDBまで届く

この流れは、下のシミュレータで実際に操作すると腑に落ちます — ヒットとミスの速度差、TTL切れ、そして更新後に古いデータが返る stale 状態まで確かめられます。

⚡ 体験: ヒット・ミス・TTL・stale を確かめる
キャッシュ(TTL 10秒)
ユーザーA(空)
ユーザーB(空)
ユーザーC(空)
ヒット 2msミス 120ms
DB
ユーザーAv1
ユーザーBv1
ユーザーCv1
取得 0ヒット率 0%平均レイテンシ 0ms

ユーザーを選んでデータを取得してみてください。1回目はミス、2回目はヒットになります。

どこにキャッシュを置くか

Web システムでは、クライアントに近い順に何層ものキャッシュが重なります。ブラウザキャッシュは HTTP の Cache-Control ヘッダで制御され、ネットワークの往復そのものを省略できる最速の層です。CDN はユーザーに地理的に近い拠点でコンテンツを返し、オリジンサーバへの到達を減らします。サーバ側では リバースプロキシ がレスポンスをキャッシュし、アプリケーション層では Redis や Memcached のようなインメモリストアが セッション 情報や DB クエリの結果を保持します。データベース自身も内部にバッファキャッシュを持っています。なお SSG は「ビルド時に全ページを作り置きする」という意味で、キャッシュの考え方を極限まで進めた形と見ることもできます。

捨て方こそが難しい

キャッシュの本当の難しさは、貯め方ではなく捨て方にあります。元データが更新されたのにキャッシュが古いままだと、ユーザーには更新前の内容が見え続けます。この「キャッシュ無効化(invalidation)」は、“計算機科学には難しいことが2つある。キャッシュの無効化と命名だ” という有名な冗談があるほど本質的な課題です。対策としては、一定時間で自動的に捨てる TTL(有効期限)方式、更新時に明示的に消す方式、キャッシュキーに内容のバージョンを含めて古いキーを自然に使わせなくする方式などがあり、実務ではこれらを組み合わせます。容量が溢れたときにどれを追い出すかも設計点で、「最も長く使われていないものから捨てる」LRU が代表的なアルゴリズムです。

典型的な落とし穴

キャッシュ特有の障害パターンも知られています。人気キーの有効期限が切れた瞬間、大量のリクエストが一斉に DB へ殺到する「キャッシュスタンピード(thundering herd)」。キャッシュサーバの再起動でヒット率がゼロになり、DB が負荷に耐えられず連鎖的に倒れる「コールドスタート」。ユーザー固有の情報を誤って共有キャッシュに載せてしまい、他人のデータが見えてしまう事故。いずれも「キャッシュは消えても正しく動く」「何をキャッシュしてよいかを明確にする」という原則で防ぎます。

設計上のトレードオフ

キャッシュは常に「新鮮さ」と「速さ・負荷」の交換です。TTL を長くすればヒット率は上がりますが古いデータを返す時間も延び、短くすれば新鮮ですが効果が薄れます。株価のように鮮度が命のデータと、プロフィール画像のように多少古くても困らないデータでは、適切な設定がまったく違います。また、キャッシュで隠された性能問題は負荷試験で見えにくく、本番のミス時に初めて顕在化することがあります。「キャッシュがなくても許容できる性能か」を先に確認し、キャッシュはあくまで上乗せとして使うのが健全な設計です。読み取りの高速化という意味では DB の インデックス と目的が似ていますが、インデックスが常に最新の答えを速く引く仕組みであるのに対し、キャッシュは複製ゆえに古くなりうる、という違いを意識しておくと使い分けが明確になります。