- API
- ネットワーク
- 技術 💻
キャッシュ設計の落とし穴8選|スタンピード・TTL・整合性
キャッシュって、入れれば速くなる魔法に見えるんですよね。
でも計算機科学には、こんな有名な格言があります。「計算機科学で難しいことは2つしかない。キャッシュの無効化と、命名だ」(フィル・カールトン)——半分冗談、半分本気の警告です。
今回は、「キャッシュがあるのに落ちた」の正体から、その「難しい方」を8つ集めました。1つでも知らなかったら儲けものです。
① キャッシュの寿命が揃うと、全滅も揃います
デプロイ直後にキャッシュを一斉投入。TTLは60秒。きれいな設計に見えますよね。
60秒後、すべてのキーが同時に消滅します。その瞬間、全リクエストがキャッシュを素通りしてDBを直撃。キャッシュがあるのに、無いのと同じ負荷が一撃で来ます。
キャッシュスタンピード(殺到)と呼ばれる定番の障害で、対策はシンプルです。
TTLにランダムな揺らぎを足す。60秒±10秒にするだけで、消滅タイミングがばらけて、崖が坂になるんです。
② 人気キーの再生成は、1人にやらせます
①の親戚で、もっと局所的なやつです。
人気商品のキャッシュが切れた瞬間、待っていた1000リクエストが全員でDBに再生成しに行きます。答えは全員同じなのに、同じ重いクエリが1000本走る。
定石は、最初の1人だけが再生成し、残りは待つか古い値を使う仕組みです。「多重リクエストの排除(request coalescing)」と呼ばれます。
キャッシュが切れる瞬間の振る舞いまで決めて、初めて設計なんですよね。
③ 「存在しない」も、キャッシュします
意外と知られていない防御が、これです。
存在しないIDへのアクセスは、キャッシュに乗らないので毎回DBまで貫通します。攻撃者がランダムなIDを大量に叩けば、キャッシュを完全に素通りしてDBを殴れるわけです。
対策は、「存在しなかった」という事実そのものを短いTTLでキャッシュすること。ネガティブキャッシュと呼ばれ、DNSの世界では昔から標準装備です。
キャッシュは「あるもの」だけでなく「ないもの」も守備範囲なんです。
④ DB更新とキャッシュ削除、順番で整合性が変わります
データを更新するとき、DBとキャッシュのどちらを先に触るか。ここに答えがあります。
定石は、「DBを更新してから、キャッシュを削除する」です。
逆(先にキャッシュ削除)だと、削除とDB更新のわずかな間に他のリクエストが来て、古いDBの値を読んでキャッシュに書き戻してしまう。以後、TTLが切れるまで古い値が生き続けます。
なお、正しい順番でもタイミング次第で極めて稀な競合は残ります。だからTTLは保険として必ず付けておくのがセットです。
「更新したのに画面が変わらない、でもしばらくすると直る」——この症状を見たら、まずこの順番を疑ってください。
⑤ キャッシュの本職は、「速くする」より「守る」です
視点が変わる話です。
キャッシュヒット率99%のシステムでキャッシュが消えると、DBへの負荷は1%→100%、つまり100倍になります。DBは100倍に耐えられる設計になっていますか?
なっていないなら、そのキャッシュはもう「高速化のおまけ」ではなく、DBを守る防波堤=落ちてはいけない基盤です。
「キャッシュはいつ消えてもいいもの」という初期の前提が、成長したシステムでは崩れている。キャッシュ障害=全面障害になっていないか、一度は棚卸ししたい観点です。
⑥ サーバー内キャッシュは、台数ぶんの世界を作ります
各アプリサーバーのメモリ内にキャッシュを持つと、速いんですが副作用があります。
サーバーが10台あれば、10個の別々のキャッシュが存在します。更新を1台が反映しても、残り9台は古いまま。ユーザーはロードバランサ次第で、リロードするたびに新旧の値を行き来します。
「更新が反映されたり戻ったりする」という怪奇現象の正体は、だいたいこれです。
対策は、共有キャッシュに寄せるか、ローカルのTTLを短くして「ズレの上限」を仕様として明言するか。ズレをゼロにするより、ズレの幅を約束する方が現実的なんです。
⑦ ホットキーは、分散しても救えません
キャッシュサーバーを何台に増やしても、アクセスが1つのキーに集中していたら、そのキーを持つ1台に全負荷が乗ります。
セール対象の1商品、話題の1投稿。分散の仕組みはキーを台数に散らすので、「1つのキーの中」は散らせないんですよね。
対策は、キーを複製して読み分ける(同じ値をkey#1〜key#Nで持つ)、あるいはアプリ側の超短命ローカルキャッシュで受ける。
負荷分散は「キーの数」に効き、「キーの偏り」には効かない。この区別が設計の分かれ目です。
⑧ 何を入れるかは、3つの掛け算で決まります
最後は、判断基準の話です。
キャッシュに入れる価値は、「再計算の重さ × 読まれる頻度 × 鮮度のゆるさ」の掛け算で決まります。
| 向いているもの | 向いていないもの | |
|---|---|---|
| 再計算の重さ | 重い集計・結合 | 軽い処理 |
| 読まれる頻度 | ランキング・マスタ情報 | めったに読まれないもの |
| 鮮度のゆるさ | 多少古くてもいいもの | 1秒の古さも許されないもの(残高など) |
向いていないものを入れても、複雑さが増えるだけの損になります。「とりあえずキャッシュ」をやめて、3つの数字で語る。設計会話が一段具体的になります。
番外編:知っておくと得する小ネタ
「古いのを出しながら、裏で更新」という技
HTTPには stale-while-revalidate という指定があります。期限切れのキャッシュを即座に返しつつ、裏で新しいものを取りに行く。ユーザーを待たせない更新の定番です。
CPUも同じことをしています
CPUのL1/L2キャッシュ、OSのページキャッシュ、CDN。コンピュータは上から下まで「近くに置いて速くする」の階層でできています。今日の原則は、全階層で同じなんですよね。
「キャッシュ消したら直った」の意味
それは直ったのではなく、④や⑥の不整合を症状ごとリセットしただけかもしれません。再発するなら、順番と共有方式を見るタイミングです。
RJCでも、キャッシュと向き合っています
RJCが携わる決済や交通のシステムは、ピーク時間の負荷が桁で跳ねる世界です。⑤の「キャッシュは防波堤」という視点は、そのまま可用性の設計なんですよね。
こういう仕組みの話を、Techチームで共有しながら開発しています。
よくいただくご質問
Q. まずどれから押さえるべきですか?
A. ④の更新順序かなと思います。コード1行の順番の話なのに、直せば「たまに古い値」がほぼ消えるんですよね。
Q. TTLはどれくらいが正解ですか?
A. ⑧の3つ目「鮮度のゆるさ」から逆算するのが正解です。「このデータ、何秒古くても業務が困らないか」を仕様として決めるのが先なんです。
Q. Redisとローカルキャッシュ、どちらを使うべきですか?
A. 整合性が大事ならRedisのような共有型、速度最優先で多少のズレを許せるならローカル+短TTLです。⑥のとおり「ズレの幅を約束できるか」で選ぶと迷いません。
Q. 未経験からでも、分かるようになりますか?
A. なります。キャッシュは「冷蔵庫に何を常備するか」と同じ発想なので、日常の感覚がそのまま設計に使える分野なんです。
消し方を決めてから、入れる
今日の8つに共通していたのは、これでした。
キャッシュは、入れるのは1行です。難しさは全部、消えるとき・消すとき・消し忘れたときに潜んでいます。
だから設計の順番は逆で、「いつ、どう消えるか」を決めてから入れる。格言が「無効化が難しい」と言っているのは、そういうことなんですよね。
みなさんが踏んだキャッシュの事故、よかったら教えてください。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。