SWR 캐시 설계기
max-age와 stale-while-revalidate 값을 설계해 헤더로 출력합니다.
stale-while-revalidate(SWR)는 캐시가 만료된 직후에도 사용자에게 묵은(stale) 응답을 즉시 돌려주고, 백그라운드에서 조용히 최신본을 다시 가져오는 캐시 전략입니다. 덕분에 만료 경계에서 발생하는 느린 요청(캐시 미스 폭주)을 없애고, 거의 항상 캐시 속도로 응답하면서도 콘텐츠를 적당히 신선하게 유지할 수 있습니다.
이 설계기는 max-age, stale-while-revalidate, 선택적 stale-if-error, 그리고 public/private 범위를 입력하면 바로 붙여 쓸 수 있는 Cache-Control 헤더를 조립해 줍니다. 동시에 각 구간을 초·분 단위로 환산하고, 요청이 들어온 시점에 따라 응답이 어떻게 동작하는지 타임라인으로 설명합니다.
Cache-Control: public, max-age=60, stale-while-revalidate=600
| 신선 구간 (0 ~ max-age) | 60초 (1분) |
|---|---|
| 백그라운드 갱신 구간 (swr) | 600초 (10분) |
| 묵은 응답 총 허용 (max-age + swr) | 660초 (11분) |
| 오류 시 제공 (stale-if-error) | — |
- 0 ~ 60초: 신선. 캐시가 즉시 응답하며 원본 요청 없음.
- 60 ~ 660초: 묵은 응답을 즉시 반환 + 백그라운드에서 원본 재검증. 사용자 대기 없음.
- 660초 이후: SWR 창 종료. 다음 요청은 동기 재검증(원본 응답까지 대기).
각 지시어가 의미하는 것
Cache-Control 응답 헤더에서 세 지시어는 캐시(브라우저·CDN)가 응답을 다루는 방식을 정합니다.
- max-age=N: 응답이 신선하다고 간주되는 시간(초). 이 구간에는 캐시가 원본에 묻지 않고 바로 응답합니다.
- stale-while-revalidate=N:
max-age가 지난 뒤에도 N초 동안은 묵은 응답을 즉시 돌려주고, 같은 순간 백그라운드에서 재검증(원본 재요청)을 수행합니다. 사용자는 기다리지 않습니다. - stale-if-error=N: 원본이 오류(5xx)나 응답 불가 상태일 때, 만료 후 N초까지는 묵은 응답을 대신 제공해 장애를 감춥니다. 가용성 보험에 해당합니다.
- public / private:
public은 CDN 등 공유 캐시도 저장 가능,private는 최종 사용자 브라우저 캐시에만 저장(사용자별 콘텐츠).
응답이 동작하는 타임라인
어떤 응답이 캐시에 저장된 시점을 0초라고 하면, 이후 요청 시점에 따라 캐시는 다음처럼 동작합니다.
- 0초 ~ max-age: 신선 구간. 캐시가 즉시 응답, 원본 요청 없음.
- max-age ~ (max-age + swr): 백그라운드 갱신 구간. 사용자에게는 묵은 응답을 바로 주고, 동시에 캐시는 원본에서 새 응답을 받아 갱신합니다. 체감 지연 0.
- (max-age + swr) 이후: SWR 창이 닫혀 더 이상 묵은 응답을 줄 수 없으므로, 다음 요청은 원본 재검증이 끝날 때까지 기다립니다(동기 재검증).
즉 max-age를 짧게, stale-while-revalidate를 길게 두면 “거의 항상 빠른 응답 + 꾸준한 백그라운드 갱신”을 얻습니다. 자세한 캐시 헤더 조립은 Cache-Control 빌더와 함께 쓰면 좋고, 임시 접근용 서명 URL의 만료 시각은 서명 URL 만료 계산기로 설계하세요.
장점과 주의점
SWR의 핵심 이점:
- 만료 경계의 “캐시 미스 천둥떼(thundering herd)”와 느린 첫 요청을 제거합니다.
- 거의 모든 응답이 캐시 속도라 TTFB가 안정적입니다.
stale-if-error까지 더하면 원본 장애 시에도 마지막 정상본으로 버틸 수 있습니다.
주의할 점:
- 사용자가 항상 최신본을 본다는 보장은 없습니다. SWR 구간 동안은 한 박자 묵은 데이터를 받을 수 있으니, 결제 금액·재고처럼 즉시 정확성이 필요한 응답에는 부적합합니다.
- 모든 브라우저·CDN이 SWR을 동일하게 지원하지는 않습니다. 미지원 캐시는 보통 max-age까지만 신선으로 취급합니다.
- 백그라운드 갱신도 결국 원본 요청을 발생시킵니다. swr 창이 매우 길면 트래픽이 적은 리소스는 갱신이 드물게 일어나 더 묵은 응답을 줄 수 있습니다.
콘텐츠 유형별 추천 프리셋
실무에서 자주 쓰는 조합을 콘텐츠 성격별로 정리했습니다. 그대로 복사하기보다 트래픽 패턴과 허용 가능한 지연(staleness)을 보고 조정하세요. s-maxage는 CDN 같은 공유 캐시 전용 신선도이며, 입력하면 이 설계기는 max-age와 함께 헤더에 함께 넣어 줍니다.
| 콘텐츠 유형 | 권장 Cache-Control | 의도 |
|---|---|---|
| 뉴스/홈 피드 (자주 변함) | public, max-age=60, stale-while-revalidate=300 | 1분 신선, 이후 5분간 백그라운드 갱신 |
| 블로그 글/제품 페이지 | public, max-age=600, stale-while-revalidate=86400, stale-if-error=604800 | 10분 신선, 하루 동안 무감지 갱신, 장애 시 1주 버팀 |
| API JSON (대시보드) | private, max-age=0, stale-while-revalidate=30 | 항상 캐시 속도 + 30초 이내 자동 갱신 |
| 해시 파일명 정적 자산 (app.abc123.js) | public, max-age=31536000, immutable | 1년 불변 — 내용 바뀌면 파일명이 바뀌므로 SWR 불필요 |
워크스루: 요청 시점별로 따라가 보기
헤더가 Cache-Control: public, max-age=60, stale-while-revalidate=300이고, 응답이 12:00:00에 캐시에 저장됐다고 합시다. 신선 만료는 12:01:00(+60초), SWR 창 종료는 12:06:00(+360초)입니다.
- 12:00:30 요청: 신선 구간 → 캐시가 즉시 응답, 오리진 호출 0.
- 12:03:00 요청: SWR 구간(만료 후 120초) → 사용자에겐 묵은 응답을 바로 주고, 캐시는 뒤에서 오리진을 재요청해 새 복사본으로 교체. 이 요청을 한 사람은 한 박자 지난 데이터를 봅니다.
- 12:03:01 요청(직후): 방금 백그라운드 갱신이 끝났다면 새로워진 응답을 받고, 타이머는 그 갱신 시각(12:03:00 근처) 기준으로 다시 60초 신선부터 시작합니다.
- 12:10:00 요청: SWR 창(12:06:00)이 닫힌 뒤이므로 묵은 응답 불가 → 오리진 동기 재검증이 끝날 때까지 기다린 후 응답.
흔한 실수 / 함정
가장 잦은 오해는 “SWR 창 안에서는 누군가 알아서 미리 갱신해 준다”고 믿는 것입니다. 백그라운드 재검증은 요청이 들어와야 트리거됩니다. 트래픽이 없으면 캐시는 점점 묵어가고, 오랜만에 들어온 요청자는 SWR 창이 이미 닫혀 결국 느린 동기 재검증을 떠안습니다. 즉 stale-while-revalidate는 “시간”이 아니라 “다음 요청”으로 갱신됩니다. 또 한 가지: no-cache나 no-store를 같이 넣으면 SWR이 무효화되니 섞지 마세요.
자주 묻는 질문
max-age=0, stale-while-revalidate=60 으로 두면 어떻게 되나요?
stale-while-revalidate는 max-age에 더해지나요, 포함되나요?
stale-if-error는 꼭 필요한가요?
이 도구는 입력값을 서버로 보내나요?
CDN과 브라우저 캐시가 다르게 동작할 수 있나요?
관련 가이드
- Cache-Control 완전 정리: 브라우저 캐시 제대로 쓰기max-age·no-cache·no-store·immutable의 진짜 의미와 리소스 유형별 권장 캐시 정책.