no-cache를 붙였는데 캐시가 남아 있던 이유 — no-store와 다르다
문제 발생
개인화된 응답이 CDN에 캐시돼 다른 사용자에게 보인 적이 있었습니다. 급히 헤더를 붙였습니다.
Cache-Control: no-cache증상은 사라진 것처럼 보였지만, 프록시 로그를 보니 응답은 여전히 저장되고 있었습니다. 이름과 동작이 달랐습니다.
원인 분석
세 지시자가 서로 다른 일을 합니다. RFC 9111의 정의를 그대로 보면 구분이 분명합니다.
no-cache— 재사용 전에 검증을 요구합니다. 응답은 검증을 위해 전달되지 않고서는 다른 요청을 만족시키는 데 사용되어서는 안 됩니다(MUST NOT). 저장은 허용됩니다.no-store— 저장 자체를 금지합니다. 캐시는 요청이나 응답의 어떤 부분도 저장해서는 안 됩니다(MUST NOT).private— 공유 캐시만 막습니다. 공유 캐시는 그 응답을 저장해서는 안 됩니다(MUST NOT) — 응답이 단일 사용자를 위한 것이라는 뜻입니다.
즉 우리가 원한 것은 "CDN에 남기지 마라"였는데 붙인 것은 "쓰기 전에 물어봐라"였습니다.
수명과 재검증도 별개입니다 — max-age는 age가 지정한 초를 넘으면 응답을 stale로 간주하게 하고, must-revalidate는 원 서버에서 성공적으로 검증되기 전까지 그 응답을 다른 요청에 재사용해서는 안 된다고 요구합니다.
해결 방안
- 의도에 맞는 지시자를 고릅니다.
Cache-Control: no-store # 민감한 개인 데이터
Cache-Control: private, max-age=0 # 사용자별 응답, 브라우저만 짧게
Cache-Control: public, max-age=300 # 모두에게 같은 공개 목록
Cache-Control: no-cache # 항상 재검증(ETag와 함께)-
no-cache는 검증자와 함께 씁니다. ETag나Last-Modified가 없으면 재검증이 곧 전체 재전송입니다 — 대역폭을 아끼려던 의도가 사라집니다. -
인증된 응답에
public을 붙일 때 특히 조심합니다. RFC가 설명하듯public은Authorization헤더가 있는 응답처럼 원래 금지되는 경우에도 캐싱을 허용합니다 — 개인화 응답에 붙이면 그 자체가 사고입니다. -
Vary를 함께 정합니다. 언어·인코딩·인증 상태에 따라 응답이 달라진다면 캐시가 그 사실을 알아야 합니다. -
stale-while-revalidate는 별도 명세입니다. RFC 9111이 아니라 RFC 5861에 정의돼 있습니다 — 쓰기 전에 대상 캐시(CDN)가 지원하는지 확인합니다.
-
실제 응답 헤더를 봅니다. 프레임워크·프록시·CDN이 각자 헤더를 덧붙이거나 덮어씁니다. 코드가 아니라 최종 응답을 확인해야 합니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.