定義
generation guard は、Service Worker 等の非同期 HTTP キャッシュで 無効化(削除)時に世代番号(generation)を上げ、各ネットワーク revalidate リクエストが 開始時点の generation を覚え、保存直前にまだ同じか確認 し、削除後に終わった古い応答がキャッシュに再書き込みされないようにするパターンである。
なぜ必要か
service-worker-stale-while-revalidate はキャッシュ hit 時に即返却し、バックグラウンドで fetch を続ける。ユーザーが キャッシュを削除 しても、削除前に開始された fetch は後から完了し cache.put で 消した原本を蘇らせうる。
性能最適化では「一瞬古い画像」は tolerable な場合もあるが、個人情報マスキング直後 や ログアウト のように原本を再表示してはいけない境界では、この race はセキュリティ事故である。mutex の代わりに軽い 整数 generation で保存資格を判定する。
動作原理
- SW グローバル:
let generation = 0。 clearCacheメッセージ:generation++→ 本文・メタ Cache Storage 削除 → ページへ ACK。- revalidate 開始:
const captured = generation。 fetch完了後:if (captured !== generation) return— 保存スキップ。- ページ: マスキング API 成功 →
await clearImageCache()(ACK 含む)→ その後 クライアント状態・画像 URL 更新。 - ログアウト等セッション終了時も同一 clear 経路。
SW がない・応答しない場合のページヘルパー:
- ページから 直接
caches.deleteを実行し、 - SW
postMessage削除を 並行 で送り、 - ACK を 最大 1.5 秒 等で制限し UI が止まらないようにする。
実務での適用
機微データでは 削除完了が UI 更新の先行条件 である。失敗時に以前の原本を「代替表示」しない方が安全。
トレードオフ
- generation は SW インスタンスメモリのみ — SW 再起動で 0 にリセットされるが、再起動時にキャッシュも空になることが多く実務では許容される。
- ACK 待ちはマスキング UX に数百 ms 遅延を与えうる — セキュリティ境界では trade-off として受け入れる。
- キー単位 generation ではなく全体 generation は 画像キャッシュ全体 clear に合う。部分無効化が多いならキー別世代やバージョンフィールドを検討。
使ってはいけない場合
- 単純 static asset(バージョンハッシュ付きファイル名)はファイル名自体が無効化なので generation なしで足りることが多い。
- サーバーだけが authoritative でクライアントキャッシュを使わないなら SW generation は不要。
- generation だけ上げて実際の
deleteをしないと guard は意味がない。
よくある間違い
cache.delete(key)後すぐ UI URL だけ変え、ACK・generation を省略。- マスキング直後も SWR で キャッシュ済み原本を先に 表示。
- ログアウト時キャッシュを残し、次ユーザーが前セッション画像を見る。
- MessageChannel ACK 無限待機 — タイムアウト・直接削除 fallback なし。
関連概念
- service-worker-stale-while-revalidate — バックグラウンド revalidate が race を生む
- presigned-url-cache-key-normalization — キー衝突とは別軸
- race-safe-async-ui-requests — UI 側 async 順序・キャンセル