웹 캐시 — 계층별 정책과 무효화로 지연을 줄이는 법
Cache-Control·ETag 같은 HTTP 캐시 헤더부터 CDN·리버스 프록시·애플리케이션 캐시까지 다계층 구조와 무효화·스탬피드 방지 운영 실무를 정리한다
2026-08-13 · 최초 발행 2025-12-03
같은 상품 페이지를 초당 수백 번 요청받는 서비스가 매번 데이터베이스까지 내려가 응답을 계산한다면 오리진은 버티지 못한다. 이미 만들어둔 응답을 빠른 저장소에 잠깐 보관해두고 재사용하는 것, 그것이 웹 캐시(Web Cache)다. 정적 자산부터 동적 HTML, API GET 응답, 프래그먼트(ESI)까지가 대상이고, 계층을 어떻게 나누고 언제 무효화할지가 설계의 핵심이다.
다섯 겹으로 쌓인 캐시
클라이언트/브라우저 캐시에서 시작해 CDN/엣지 캐시, 리버스 프록시(Nginx/Varnish), 애플리케이션 내부 캐시(메모리/Redis), 데이터베이스/스토리지 캐시까지 다계층 구조를 이룬다. 상위 계층의 히트율이 오를수록 하위 계층과 오리진이 받는 부하는 급격히 줄어든다.
캐시 키는 경로와 쿼리스트링을 정규화한 값에 헤더(Vary: Accept-Encoding, Accept-Language, Authorization 등)와 쿠키·디바이스·로캘 세그먼트를 더해 만든다. Vary를 최소한으로 쓰고 개인화 범위를 세그먼트화하며 불필요한 쿠키를 무시하는 정책을 두지 않으면 키가 기하급수로 불어나는 키 폭발이 벌어진다.
헤더가 신선도를 결정한다
Cache-Control(max-age, s-maxage, public/private, no-store, no-cache), ETag/If-None-Match, Last-Modified/If-Modified-Since, Expires가 기본 제어 헤더다. CDN에서는 Surrogate-Control, stale-while-revalidate, stale-if-error를 더해 가용성을 강화한다.
일관성은 TTL 기반 만료와 조건부 재검증(ETag/IMS), 이벤트 기반 무효화(PURGE/BAN/Surrogate-Key)를 병행해 관리한다. 변경 이벤트와 캐시 키를 정확히 매핑하고, 캐시 스탬피드를 막기 위한 락·재검증 합류 설계가 필수다.
저장소는 초저지연의 메모리 캐시와 용량이 유리한 디스크 캐시 중에서 고르고, 압축(gzip/br)과 범위요청 지원을 함께 넣는다. 페이지·오브젝트·프래그먼트(ESI) 레벨을 혼합하면 히트율과 일관성의 균형을 잡을 수 있다.
관측은 히트율(HR), 백엔드 오프로딩(Origin Offload), P50/P95/P99 레이턴시, 오류율, 캐시 키 카디널리티 같은 지표로 하고, Age·Via·X-Cache 같은 디버그 헤더를 표준화해 로그·메트릭·트레이싱에 연계한다. 안정성 측면에서는 stale-if-error·stale-while-revalidate로 오류·스파이크 상황에도 응답을 지속시키고, 캐시 우회(Cache-Bypass)·서킷브레이커·급증 트래픽 완충 레이어로 방어선을 세운다.
자산부터 쿼리까지, 계층별로 다르게 캐싱한다
정적 자산은 파일 핑거프린팅(app.abc123.js)에 Cache-Control: public, max-age=31536000, immutable을 붙이고 CDN에 태워 배포 시 URL이 바뀌는 것만으로 안전하게 영구 캐시하고 오리진 부하를 최소화한다. 동적 HTML·프래그먼트는 비로그인·세그먼트별로 캐시하고 개인화 영역은 ESI/CSR로 분리하며, 카테고리·홈 페이지는 TTL과 Surrogate-Key 조합으로 대량 무효화를 지원한다.
API 응답 캐시는 GET·멱등 엔드포인트를 대상으로 s-maxage와 최소화된 Vary를 쓰고, 토큰 의존성을 없애거나 Authorization에 따라 private 처리한다. 304 재검증으로 데이터 신선도와 트래픽 절감을 동시에 얻는다. 데이터·쿼리 캐시는 Redis/Memcached로 읽기 집중 쿼리를 캐시하고 write-through 또는 캐시 어사이드 + Pub/Sub 무효화를 조합하며, 키 정규화와 TTL 지터로 동시 만료를 완화한다. 엣지 사이드 컴퓨팅에서는 CDN Functions/Workers로 헤더 재작성, 키 정규화, 인증 쿠키 스트리핑, 조건부 캐싱을 구현하고 원격 설정으로 무중단 정책 변경이 가능하다.
히트율이 오르면 얼마나 달라지는가
가정 조건을 하나 잡아보면 감이 잡힌다. 평균 요청 10,000 RPS, 오리진 처리 20ms, CDN/프록시 캐시 3ms라고 할 때, 히트율 80%를 달성하면 오리진이 받는 RPS는 2,000으로 줄어든다(−80%). 오리진 CPU·DB 사용량도 이 비율에 비례해 줄어든다. 사용자 체감 레이턴시는 0.8×3ms + 0.2×(3ms+20ms) = 2.4ms + 4.6ms = 7.0ms 수준으로, 캐시가 없을 때보다 크게 단축된다. egress·오리진 트래픽이 줄면서 CDN·클라우드 비용도 최적화되고, 고부하 상황에서 스케일 아웃까지 걸리는 지연도 캐시가 흡수해준다. 정성적으로는 배포·데이터 변경 시 안전한 롤아웃이 가능해지고, SLO 준수와 피크 트래픽 흡수로 비즈니스 연속성이 강화된다.
요청이 캐시를 통과하는 경로
계층별로 무엇이 다른가
| 계층 | 성능(지연) | 확장성 | 일관성 | 안정성 | 운영 편의 |
|---|---|---|---|---|---|
| 브라우저 캐시 | 매우 우수 | 클라이언트 수만큼 자동 확장 | 낮음(TTL 의존) | 우수 | 헤더 중심, 제어 한계 |
| CDN/엣지 | 매우 우수 | 글로벌 POP 확장 | 중간(서로게이트 키/TTL) | 매우 우수 | 콘솔/API로 관리 용이 |
| 리버스 프록시 | 우수 | 수평 확장 | 중간(재검증/무효화) | 우수 | 인프라 운영 필요 |
| 앱 내부(메모리/Redis) | 최고(메모리) | Redis 클러스터로 확장 | 높음(정밀 무효화 가능) | 우수 | 코드/운영 복합 관리 |
| DB/스토리지 캐시 | 중간 | 엔진 종속 | 높음(엔진 수준) | 우수 | 튜닝 난이도 존재 |
무효화와 안정성, 놓치면 안 되는 것들
정적 자산은 길게, 동적 응답은 짧은 TTL에 재검증·무효화를 섞는 것이 기본 정책이다. Vary를 최소화하고 Authorization·Cookie 처리를 명확히 해야 키 폭발을 막는다. 무효화는 배포 시 파일 핑거프린팅과 Surrogate-Key로 대량 처리하고, 데이터 변경은 도메인 이벤트 → 키 매핑 → PURGE/BAN/DEL 트리거 파이프라인으로 흐르게 한다. 안정성은 stale-while-revalidate/stale-if-error 활성화, 캐시 스탬피드 방지(락, 요청 합류, TTL 지터), 캐시 우회 스위치와 백프레셔·서킷브레이커로 확보한다. 보안 측면에서는 개인화·민감정보 응답을 private/no-store로 두고 인증 헤더 기반 응답은 퍼블릭 캐시를 금지하거나 Vary: Authorization을 적용하며, 캐시된 PII 로그·메타데이터를 관리하고 TLS·서명 URL과 함께 써야 한다.
헤더와 설정을 직접 확인하기
사전조건은 curl 7.79+다.
# 사전조건: curl 7.79+
curl -I https://static.example.com/app.abc123.js
# 기대: Cache-Control: public, max-age=31536000, immutable
# ETag 또는 strong fingerprint로 무효화 보장
리버스 프록시 캐시는 Nginx 1.24+, Linux x86_64 환경에서 다음과 같이 구성한다.
# 환경: Nginx 1.24+, Linux x86_64
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=zone_app:256m max_size=20g inactive=60m use_temp_path=off;
map $http_cookie $bypass_login {
default 0;
"~*session|auth" 1; # 로그인 쿠키 감지 시 우회
}
server {
listen 443 ssl;
server_name api.example.com;
location / {
proxy_pass http://app_upstream;
proxy_cache zone_app;
proxy_cache_valid 200 301 302 10m;
proxy_cache_lock on; # 스탬피드 방지
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
proxy_cache_bypass $bypass_login; # 개인화 우회
add_header X-Cache $upstream_cache_status always;
}
}
정적은 강하게, 동적은 세그먼트로
다계층 구조와 정책 자동화를 갖추면 신뢰성 높은 성능 향상을 얻을 수 있다. 핵심은 캐시 키 정규화, TTL·재검증·무효화의 적절한 혼합, 스탬피드·오류 대응, 관측 가능성 확보다. 정적 자산은 강한 캐시로, 동적·개인화 콘텐츠는 세그먼트·프래그먼트 전략으로 나눠 단계적으로 도입하는 편이 안전하다.