← 一覧へ戻る

Claude が書いた記事

キャッシュは実際どこに置かれてるか ── 自前ディスクのハッシュ名と、Redis に保管を肩代わりさせる構成

キャッシュってファイルで保管されてるん? という素朴な疑問から、ディスクに「ハッシュ名 + 索引」で置く自前型と、Redis にまるごと肩代わりさせる型の 2 つを並べて、Redis をキャッシュに使うときの定番構成 (Cache-Aside・同居 vs 中央共有・TTL と押し出し) まで辿った学習ログ。

俺の最初の疑問

前に Front Door のオブジェクトチャンキング を調べてて、「大ファイルを 8 MB のチャンク単位でキャッシュする」という話に行き着いた。そこで素朴に詰まった。

そのキャッシュって、結局どこに・どんな形で置かれてるん? ファイルなんか?

そしてもう一つ。キャッシュの保管といえば Redis という名前をよく聞く。あれを使うとき、みんなだいたいどう組んでるんや? という定番の構成も知りたかった。

まず一言でいうと

キャッシュの実体は、ディスク (ファイル) か RAM (メモリ) のどっちか。速くて消えやすいのが RAM、遅いけど大容量で消えないのがディスク。そして大事なのは、どっちに置くにしても、URL をそのまま置くんやなくて、間に必ず「変換」が挟まること。

  • 自前ディスク型 … URL をハッシュ化した名前のファイルとして置く。例: /video.mp4a1b2c3d4... という名前のファイル。
  • Redis 型 … 保管も変換も Redis という別ソフトに丸投げする。Web サーバー側は cache:/video.mp4 みたいなきれいなキー名でデータを指すだけ (読むときは GET キー、書くときは SET キー 値 と、コマンドにキーを乗せて送る)。ハッシュ化も置き場所も Redis 任せ。

出発点はどっちも URL。違うのは「自分でディスクに置いて索引も自分で持つ」か、「Redis に置き場所ごと肩代わりさせる」かや。

何と比べるとわかるか

2 つの保管モデルを並べると、役割分担の差がはっきりする。その前に登場人物を 1 つ ── 自前ディスク型でキャッシュを持つ役は、前段に立つリバースプロキシや CDN エッジ (Front Door のような前段の中継役) や。以下これをプロキシと呼ぶ。一方 Redis 型でキャッシュを叩くのは Web サーバー (アプリ) 側になる。

自前ディスク型 (CDN エッジ等)Redis 型 (インメモリ KVS)
置き場所ローカルディスク / SSD全部 RAM (メモリ)
名前 / 鍵URL をハッシュ化した不可逆なファイル名Web サーバーはきれいなキーのまま (cache:/video.mp4)
ハッシュ化を誰がやるか自分 (プロキシ) がやるRedis が裏でやる。Web サーバーは気にしない
索引 (どこにあるか表)自分でメモリに持つ (URL → ハッシュ)Redis 内部のハッシュテーブルが索引そのもの
消えたら索引は Miss で再構築 or 永続化で復元Redis が裏でディスクに書いて再起動後に復活
向いてる規模巨大ファイルの配信 (CDN)アプリのデータ・セッション・小さめの値

ざっくり言うと、自前ディスク型は「保管も索引も DIY」、Redis 型は「保管屋に外注」。次の節からは、まず DIY 側の「ハッシュ名で置いたら、どうやって URL から探すんや?」という引っかかりを潰す。

2 つの保管モデル。左の自前ディスク型は『保管も索引も自分でやる』。右の Redis 型は『キーを投げるだけで保管屋 (Redis) が全部やる』。共通点は、どちらも URL を出発点に、間で必ず変換が挟まること。

何が問題なのか ── ハッシュ名は不可逆。じゃあどう探す?

自前ディスク型で、まず引っかかったのがここ。ファイル名が a1b2c3d4... というハッシュ値なら、そのファイル名から元の URL は復元できへん。ハッシュ関数 (MD5 / SHA-256 等) は一方向で、戻せないのが仕様や。

ここで誤解しやすいけど、そもそも「ファイル名から URL を復元」する必要はない。やりたいのは逆や。

ユーザーが /video.mp4 を要求してきた → このファイル、キャッシュにあるか?

つまり手元にあるのは常に URL の方。だから URL をその場でハッシュ化して a1b2c3d4... を計算し、「その名前のファイルがディスクにあるか」を見ればいい。これは順引き (URL → ハッシュ) であって、逆引き (ハッシュ → URL) やない。

そのために、プロキシは「URL → ハッシュ名」の対応表をメモリに索引として持つ。リクエストが来たら索引を引いて、ヒットすればディスクのファイルを返し、無ければオリジンに取りに行って同じハッシュ名で保存する。

順引きの流れ。ファイル名から URL を戻す (逆引き) んやなくて、手元の URL からハッシュを計算して探す (順引き)。だからハッシュが不可逆でも困らへん。索引は『URL → ハッシュ名』の対応表で、ヒット率を上げるためメモリに置く。

ここで出てくる「索引 (インデックス)」という言葉に、もう一つ引っかかった。「メモリ上の索引」と言うけど、それをディスクに保存したら索引やなくなるんか? と。

答えはノー。索引は置き場所の名前やなくて、「目的のデータを速く見つける仕組み」の名前。本の索引と同じで、巻末に印刷されてても (ディスク = 消えない・遅い)、机に広げてても (メモリ = 速い・片付けたら消える)、どっちも索引や。役割は同じで、速さと永続性が違うだけ。

ついでに紛らわしいのが「索引」と「目次」。これは別物や。目次は本の前にあって、章が出てくる順に並んだ全体の地図索引は本の後ろにあって、言葉 (キー) を引くとページ番号が分かる対応表。キャッシュで言う索引は、完全に後者 ──「URL (キー) → データの居場所 (ページ番号にあたる)」を引くための表や。

索引 ≠ 目次。目次 (左上・✗) は『出てくる順』の地図。索引 (右上・✓) は『言葉 (キー) → ページ番号』の対応表で、引きたい語から一発で飛べる。キャッシュの索引 (下) はこれと同じ形 ──『キー → 在りか』。だから順番で眺める目次やなくて、キーで引く索引と呼ぶ。

たとえ話 ── コインロッカーと、フロント預かり

2 つのモデルは、荷物の預け方で考えるとわかりやすい。

  • 自前ディスク型 = コインロッカー。 自分で空きロッカーを選んで荷物を入れ、番号 (ハッシュ名) を覚えておく。どの荷物がどの番号かは自分のメモ (索引) で管理する。番号から中身は分からんけど、自分の荷物が何番かは分かってるから問題ない。
  • Redis 型 = ホテルのフロント預かり。 荷物に名前 (キー) を付けてフロントに渡すだけ。どこにどうしまうかはフロント (Redis) が全部やる。受け取るときも名前を言うだけ。自分は棚番号を一切知らんでいい。

DIY で自由が利くのがコインロッカー、丸投げで楽なのがフロント預かり。キャッシュもこの 2 択や。

Redis をキャッシュに使うときの定番構成

ここからが二つ目の疑問。Redis をキャッシュに使うとき、現場でだいたい決まって出てくる組み方が 3 つある。Redis そのものの中身 (通信プロトコルやメモリ構造) には踏み込まず、「どう組むか」のレイヤーだけ見る

その前に Redis を一言で。ここで 別々の 2 つを分けておきたい ── まとめて言うと「KVS やから速い」と誤解しやすいからや。

  • 速さの正体は「メモリに置いてる」こと。Redis はデータを全部 RAM に持つ (インメモリ)。ディスクを読まんからマイクロ秒で返る。これは置き場所の話で、特別なハードやない ── ただの RAM や。
  • KVS (Key-Value ストア) は「引き方」の名前キー → 値 でしか引けへん、というアクセスの型のこと。速さともハードとも無関係で、RDB (テーブルを JOIN や WHERE で引く DB) と対比した分類名にすぎん。Redis でいえば、その「連想配列 (キー → 値 の表)」を自分のプログラムの中やなくて、外に別サービスとして立てて呼び出す形や (中の実装が何か ── ハッシュテーブルか木か ── や、どこに置いて何で繋ぐかは、KVS という分類とは別の話)。

念のため ── 同じ KVS でもディスクに置くやつ (DynamoDB 等) は数ミリ秒で、マイクロ秒やない。Redis が速いのは「KVS やから」やなくて「メモリに置いてるから」。そしてキャッシュは「この URL (キー) の値ある?」と聞くだけ ── キー一発引きしか要らんから、KVS の形がそのままハマる。メモリは高いので、何でも入れるんやなく「よく使う・速さが命のデータ」だけ置く。

ちなみに「キー」と聞くと施錠の鍵を思い浮かべるけど、KVS のキーはロックを開ける鍵やないapple を引くと意味が返る辞書の見出し語や、番号を出すと荷物が返るコインロッカーの札みたいに、「それを言えば値が返ってくる名前・目印」のことや。英語の key が「鍵」と同じ綴りなだけ。

KVS の『キー』はドアの鍵 (lock・左の✗) やない。辞書の見出し語やロッカーの番号札 (右の✓) みたいに『それを言えば値が返る名前・目印』のこと。下は Redis の実例で、user:100 や cache:/video.mp4 というキーを言えば値が返る。英語の key が鍵と同綴りなだけ。

① Cache-Aside ── 一番よく見る基本形

Cache-Aside (キャッシュアサイド / 別名 Lazy Loading) は、Redis キャッシュの最頻出パターン。アプリが自分でキャッシュを見に行き、無ければ元データから読んで自分で書き戻すやり方や。

  1. リクエストが来たら、まず Redis に GET cache:/video.mp4 で聞く。
  2. あれば (Cache Hit) … その値をそのまま返す。元データ (DB やオリジン) は触らない。爆速。
  3. 無ければ (Cache Miss) … 元データから読んで、ユーザーに返すと同時に Redis へ SET cache:/video.mp4 <値> EX 3600 で書き戻す (次回からはヒットする)。

ポイントは、Redis はあくまで「脇に置く (aside) 速い層」で、本当の正は元データ側にあること。Redis が落ちても、Miss が増えて遅くなるだけでデータは失われへん。(Azure のアーキテクチャ集にも Cache-Aside パターンとして載ってる。)

Cache-Aside の流れ。①〜⑤ は全部 Web サーバーが出す ── Hit は ① で値が返って ⑤ そのままユーザーへ (爆速)。Miss のときだけ ② Web サーバーが元データを読む → ③ 値 → ④ Web サーバーが Redis へ SET (書き戻し) → ⑤ 返す。肝は『Redis と元データは直接つながらず、Web サーバーが両方を仲介する』こと。これが Cache-Aside (脇に置く) の意味で、正は元データ側やから Redis が落ちても遅くなるだけ。

② 同居 か 中央共有 か ── Redis をどこに置くか

Redis をどのマシンで動かすかで、定番が 2 つに分かれる。

  • 同居 (小〜中規模) … Web サーバーと同じマシンに Redis を入れ、localhost:6379 (ループバック) で話す。ネットワークを跨がへんから速いし、構成も安い。難点は、同じマシンのメモリ・CPU を奪い合うことと、Web サーバーを増やしても各自バラバラのキャッシュになること。
  • 中央共有 (中〜大規模) … Redis を独立したマシン (またはマネージドサービス) に分け、複数の Web サーバーから1 つの Redis を共通の保管庫として見る。Azure Cache for Redis や Amazon ElastiCache がこれ。Web サーバーを何台に増やしてもキャッシュは 1 つに揃う。難点はネットワーク越しのわずかな遅延とコスト。

Web サーバーを 1 台から複数台にスケールアウトする瞬間に、同居の弱点 (キャッシュがバラける) が効いてくる。だから「最初は同居、増えたら中央共有」が定石や。

③ あふれたら押し出す ── TTL と eviction

メモリは有限やから、何も考えずに入れ続けるといつか満杯になる。Redis キャッシュでは、これを 2 段構えでさばくのが定番。

  • TTL で時間切れ失効SET ... EX 3600 のように鍵ごとに寿命を付ける。1 時間経ったら Redis が勝手に消す。古い情報を返さないための仕掛け。
  • maxmemory + 押し出し (eviction)maxmemory 2gb で上限を決め、maxmemory-policy allkeys-lru を付けると、上限に達したとき一番長く使われてない鍵 (LRU = Least Recently Used) から自動で捨てる。これでメモリが溢れてクラッシュするのを防ぐ。(Redis 公式の eviction の説明。)

キャッシュは「消えても困らない」前提のデータやから、あふれたら古いものから容赦なく捨てるのが正しい。TTL が「鮮度切れで捨てる」、eviction が「場所が無いから捨てる」── 捨てる理由が違うだけで、どっちも「貯め込まない」ための仕組みや。

圧縮してから保存される? ── 加工するのは「誰か」

メモリでもディスクでも、キャッシュに入るデータが圧縮済みのことはある。でも キャッシュが既定で勝手に圧縮するわけやない。加工するのが誰かで決まる。

  • HTTP / CDN キャッシュ (ディスク側) … オリジンから受け取ったレスポンスをその形のまま保存する。オリジンが gzip で返したら圧縮されたバイト列のまま置いて、同じ圧縮を受け付けるクライアントへ流す。これは前記事Content-Encoding そのもので、圧縮あり版と無し版は別のキャッシュエントリになる (Vary: Accept-Encoding でキーが割れる)。CDN 自身がエッジで圧縮する設定もある (Front Door なら 8 MB 未満が対象) ── これは「キャッシュ側が加工する」例。
  • Redis (メモリ側) … 渡されたバイト列をそのまま置く。SET の値を勝手に gzip したりはせん。圧縮したいならアプリが SET の前に圧縮し GET の後に解凍する (メモリ節約の定番)。やるかどうかはアプリの判断。

圧縮以外の「加工」も同じ筋で、やる主体が決まってる ── シリアライズ (アプリがオブジェクト → バイト列にする)、チャンク分割 (CDN が 8 MB 単位に割る)、保存時の暗号化 (マネージドの at-rest 暗号化)。要はキャッシュが魔法で変換するんやなくて、オリジン / アプリ / 明示の設定のどれかが加工した結果が、そのまま保存される

出てきた言葉の早見表

言葉つまり何の話か
KVS (Key-Value ストア)キー → 値 でしか引けない「引き方」の型。RDB と対比した分類名で、速さとは別軸 (速いのはメモリに置くから)
索引 (インデックス)目的のデータを速く見つける仕組み。メモリでもディスクでもいい (置き場所の名前やない)
順引き / 逆引き順引き = URL → ハッシュ (使うのはこっち) / 逆引き = ハッシュ → URL (ハッシュは不可逆なので無理)
Cache-Asideアプリが自分でキャッシュを見て、無ければ元から読んで書き戻す基本パターン
Cache Hit / MissHit = キャッシュにあった / Miss = 無かった (元データを読みに行く)
TTL (EX)鍵ごとの寿命。時間が来たら自動で消える
eviction (maxmemory + LRU)メモリ上限に達したとき、古い鍵から押し出して捨てる仕組み
同居 / 中央共有Redis を Web サーバーと同じマシンに置くか (localhost:6379)、独立させて複数 Web サーバーで共有するか
次に答える、設計者の問い で、それがなんやねん?

で、自分のシステムに Redis キャッシュを足すとき、どこに置くんや?

  • 同居 (localhost:6379) か、中央共有 (Azure Cache for Redis 等のマネージド) か。 判断軸は「Web サーバーをスケールアウトするか」。1 台のままなら同居で十分 (速い・安い)。複数台に増やすなら、各自バラバラのキャッシュでヒット率が落ちるので、中央の 1 つに寄せる。増設の予定があるなら最初から中央共有に倒すのも手。
  • 責任境界はどこか。 Redis が持つのは速い複製 (脇に置いた層) だけで、データの正は元データ (DB / オリジン) 側にある。だから Redis が落ちても「遅くなる」で済む設計にする ── キャッシュを正にしてはいけない。

これに答えられると強い ── 「とりあえず Redis 挟む」で止めず、規模に合わせて置き場所を選び、落ちても壊れない責任境界を引ける側に立てる

深掘りメニュー 次におすすめのトピック

この記事の続きとして、次に掘るといいトピック。

  • Cache-Aside の兄弟 (Read-Through / Write-Through / Write-Behind) ── 「書き戻しを誰がやるか・いつやるか」で分かれる別パターン。Cache-Aside の次に押さえると書き込み側の設計が見える。
  • キャッシュの一貫性問題 (stale data / cache invalidation) ── 元データが変わったのにキャッシュが古いまま、をどう防ぐか。TTL だけでは足りない場面の話。
  • Redis の永続化 (RDB スナップショット / AOF) ── 「再起動で消えないための裏の書き出し」を一段深掘り。メモリなのに復活できる仕組み。
  • thundering herd / cache stampede ── 人気の鍵が一斉に失効した瞬間、Miss がオリジンに殺到する問題。中央共有 Redis で効いてくる。