정의
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 순서·취소