キャッシュ層を棚卸しして古いデータを防ぐ
「更新したのに画面が変わらない」。この報告は、再現しないことが多く、開発側が最も扱いに困るバグの一つです。原因の多くは実装ミスではなく、どこに・どれだけの期間データを持つかを設計時に決めていなかったことにあります。この記事は設計工程の話として、キャッシュの層を先に棚卸しし、無効化の契機を決めてから実装する方法をまとめます。
なぜ「古いデータが見える」バグは起きるのか
キャッシュは性能のために後から足されがちです。「遅いからここにも入れよう」を繰り返すと、誰もすべての層を把握していない状態になります。すると更新した瞬間に、ある層は新しく、ある層は古いままという食い違いが生まれます。
- 画面A(一覧)は新しいのに、画面B(詳細)は古い
- 自分の端末では直ったが、同僚の端末では古いまま
- リロードすると直るが、しばらくするとまた古くなる
どれも、キャッシュの層と寿命が頭の中にしかないことの症状です。
設計時に作る「キャッシュ層の棚卸し表」
実装に入る前に、データが通る経路上のキャッシュを全部書き出します。最低限、次の4層を見ます。
層 | 例 | 寿命の決まり方 | 無効化の契機 |
|---|---|---|---|
ブラウザ | HTTPキャッシュ、Service Worker | Cache-Control の max-age | URLにバージョンを付ける、再検証 |
CDN | Firebase Hosting、CloudFront など | s-maxage、CDN側の設定 | パージ操作、ファイル名のハッシュ |
アプリ(サーバー) | Rails.cache、メモ化 | expires_in | 更新時に該当キーを削除 |
クライアント状態 | SWR、React Query、Redux | staleTime など | 更新後に再取得(invalidate) |
この表の「無効化の契機」欄が空白の行は、そのまま将来のバグ候補です。空欄を埋めるまで実装に進まない、というルールにするだけで効果があります。
層ごとに決めること
1. 何をキャッシュしてよいか
公開ページの静的な部分はキャッシュ向きです。一方、ログイン中の本人にだけ見えるデータや、更新直後に必ず見せたいデータ(保存した結果の画面など)は、CDNに載せないと決めておきます。ユーザー別のレスポンスには Cache-Control: private, no-store を明示します。
2. 寿命をいくつにするか
「何秒古くてもよいか」を機能ごとに決めます。ランキングなら5分でも許されますが、自分が今保存した内容が古く見えるのは許されません。許容できる古さを仕様として書いておくと、後から「バグか仕様か」で揉めません。
3. 誰が無効化するか
更新処理の中で、同じトランザクションの流れとして該当キャッシュを消す箇所を1か所に集めます。Rails の例です。
def update
@article.update!(article_params)
Rails.cache.delete(["article", @article.id])
Rails.cache.delete(["article_list", @article.project_id])
end更新箇所が複数に散らばると、1か所だけ消し忘れます。更新と無効化を同じメソッドにまとめるのが基本です。
「反映されない」報告を受けたときの切り分け順
報告が来たら、外側(ユーザーに近い層)から順に潰すと速く特定できます。
- ブラウザ: シークレットウィンドウ、または DevTools の Network で「Disable cache」にして再現するか。直るならブラウザ層
- ブラウザ(レスポンス): Network タブで該当リクエストを開き、Size 列が
(disk cache)になっていないか。レスポンスヘッダの Cache-Control と Age も見る - CDN: レスポンスの
AgeやX-Cacheヘッダを確認し、CDNが返していないか見る - アプリ: サーバーのログで、DBまで届いているか、キャッシュから返しているかを確認する
- クライアント状態: 更新後に再取得が走っているか、ライブラリのキャッシュ設定を確認する
どの層で直ったかを記録しておくと、棚卸し表の抜けがそのまま見つかります。
失敗例: 層ごとに正しいのに全体で間違う
サーバーは更新時にキャッシュを消していて、CDNは60秒、ブラウザは5分の設定だった例です。各層は個別には妥当ですが、組み合わせると最長で約6分間、更新が見えません。層ごとの寿命は足し算になる、と覚えておくと設計時の見積もりがしやすくなります。
Bugoon での実践
「反映されない」報告は、報告者の環境の情報がないと切り分けが始まりません。Bugoon のウィジェットを埋め込むと、報告時のスクリーンショットに加え、ブラウザ・OS・画面サイズ、操作ステップ、Console のエラーやネットワークエラーが自動で添付されます。上の切り分け順の最初の数手を、開発者が聞き返さずに始められます。GitHub Issue に連携すれば棚卸し表の更新タスクとして起票でき、MCP サーバー経由で Claude Code や Cursor に渡して調査させることもできます。