<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Cache-Control on jyukki's Blog</title><link>https://jyukki.com/tags/cache-control/</link><description>Recent content in Cache-Control on jyukki's Blog</description><generator>Hugo -- 0.147.0</generator><language>ko-kr</language><lastBuildDate>Fri, 03 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jyukki.com/tags/cache-control/index.xml" rel="self" type="application/rss+xml"/><item><title>백엔드 커리큘럼 심화: HTTP 캐싱·ETag·재검증(Revalidation) 운영 플레이북</title><link>https://jyukki.com/learning/deep-dive/deep-dive-http-caching-etag-revalidation-playbook/</link><pubDate>Fri, 03 Apr 2026 00:00:00 +0000</pubDate><guid>https://jyukki.com/learning/deep-dive/deep-dive-http-caching-etag-revalidation-playbook/</guid><description>캐시 적중률 숫자만 보는 단계를 넘어, Cache-Control/ETag/재검증 정책을 서비스 위험도와 데이터 신선도 기준으로 설계하는 실무 플레이북입니다.</description><content:encoded><![CDATA[<p>대부분의 팀이 HTTP 캐싱을 &ldquo;붙이면 빨라지는 옵션&rdquo; 정도로 생각합니다. 실제 운영에서는 반대입니다. 캐싱은 성능 기능이 아니라 <strong>일관성·비용·장애 반경을 동시에 건드리는 아키텍처 정책</strong>입니다. 5ms 빨라지는 것보다 더 중요한 건, 어떤 데이터는 30초까지 오래 보여도 되는지, 어떤 데이터는 1초만 늦어도 사고인지 합의하는 일입니다.</p>
<p>이 글은 CDN, 브라우저, API 서버가 각각 어떤 책임을 가져야 하는지, 그리고 ETag/재검증을 어디까지 자동화해야 하는지 실무 숫자 기준으로 정리합니다.</p>
<h2 id="이-글에서-얻는-것">이 글에서 얻는 것</h2>
<ul>
<li><code>Cache-Control</code>, <code>ETag</code>, <code>Last-Modified</code>를 문법이 아니라 <strong>의사결정 도구</strong>로 쓰는 기준을 가져갈 수 있습니다.</li>
<li>캐시 적중률(히트율)만 보는 단순 지표에서 벗어나, <strong>신선도 위반률·원본 부하·재검증 비용</strong>까지 함께 보는 운영 관점을 잡을 수 있습니다.</li>
<li>엔드포인트별로 캐시 전략을 구분하고, 장애 시 안전하게 강등(degrade)하는 **실무 우선순위(숫자·조건)**를 바로 적용할 수 있습니다.</li>
</ul>
<h2 id="핵심-개념이슈">핵심 개념/이슈</h2>
<h3 id="1-캐시-정책은-url-단위가-아니라-데이터-위험도-단위로-설계해야-한다">1) 캐시 정책은 URL 단위가 아니라 &ldquo;데이터 위험도&rdquo; 단위로 설계해야 한다</h3>
<p><code>/api/products/{id}</code> 같은 경로 기준으로 정책을 통일하면 거의 항상 실패합니다. 같은 경로여도 데이터의 위험도는 다릅니다.</p>
<ul>
<li>재고/가격/주문 상태: 신선도 민감(짧은 TTL 또는 재검증 강제)</li>
<li>상품 설명/이미지 메타: 지연 허용(긴 TTL + stale 허용)</li>
<li>공지/정책 문서: 매우 긴 캐시(버전 파일 + immutable)</li>
</ul>
<p>실무에서는 먼저 &ldquo;허용 가능한 오래됨(staleness)&ldquo;을 숫자로 잡고, 그 다음 캐시 전략을 매핑합니다.</p>
<ul>
<li>Tier A(강한 신선도): 허용 오래됨 1~3초</li>
<li>Tier B(보통): 10~60초</li>
<li>Tier C(정적/준정적): 1시간~7일</li>
</ul>
<p>이 분류는 <a href="/learning/deep-dive/deep-dive-slo-sli-error-budget/">SLO/SLI/Error Budget</a>과 같이 정의해야 조직 합의가 됩니다.</p>
<h3 id="2-cache-control은-성능-옵션이-아니라-계약서다">2) Cache-Control은 &ldquo;성능 옵션&quot;이 아니라 &ldquo;계약서&quot;다</h3>
<p>자주 보이는 실수는 <code>max-age=600</code>만 넣고 끝내는 것입니다. 운영에서 필요한 건 캐시 생명주기 전체 계약입니다.</p>
<ul>
<li><code>max-age</code>: 사용자/엣지에서 신선하게 간주하는 시간</li>
<li><code>s-maxage</code>: CDN(shared cache) 전용 정책</li>
<li><code>stale-while-revalidate</code>: 백그라운드 재검증 허용 구간</li>
<li><code>stale-if-error</code>: 원본 장애 시 낡은 응답 임시 제공 구간</li>
<li><code>must-revalidate</code>: 만료 후 재사용 제한</li>
</ul>
<p>권장 시작점(조회 API 기준):</p>
<ul>
<li>Tier A: <code>Cache-Control: private, max-age=0, must-revalidate</code></li>
<li>Tier B: <code>Cache-Control: public, max-age=30, s-maxage=60, stale-while-revalidate=30</code></li>
<li>Tier C: <code>Cache-Control: public, max-age=3600, s-maxage=86400, immutable</code></li>
</ul>
<p>핵심은 &ldquo;모든 API 캐시&quot;가 아니라, 사용자 영향과 장애 비용을 같이 보고 정책을 나누는 것입니다. <a href="/learning/deep-dive/deep-dive-api-rate-limit-backpressure/">API Rate Limit/Backpressure</a>와 결합하면 원본 보호 효과가 커집니다.</p>
<h3 id="3-etaglast-modified는-트래픽-절감-장치이면서-동시성-보호-장치다">3) ETag/Last-Modified는 트래픽 절감 장치이면서 동시성 보호 장치다</h3>
<p>ETag를 단순 304 최적화로만 보면 절반만 본 겁니다. 실무에서는 두 가지 역할이 중요합니다.</p>
<ol>
<li><strong>읽기 재검증 비용 절감</strong>: 바뀌지 않은 응답은 본문 없이 304 반환</li>
<li><strong>쓰기 충돌 방지</strong>: <code>If-Match</code>로 낙관적 동시성 제어</li>
</ol>
<p>운영 규칙 예시:</p>
<ul>
<li>엔티티 버전이 있는 리소스: 강한 ETag(<code>&quot;v42&quot;</code>) 우선</li>
<li>집계/동적 조합 응답: 약한 ETag(<code>W/&quot;...&quot;</code>) 허용</li>
<li>수정 API(PUT/PATCH): <code>If-Match</code> 없는 요청은 428 또는 412 처리</li>
</ul>
<p>즉, ETag는 성능과 정합성을 동시에 잡는 도구입니다. 이 부분은 <a href="/learning/deep-dive/deep-dive-idempotency/">Idempotency 설계</a>나 <a href="/learning/deep-dive/deep-dive-mysql-isolation-locks/">DB 락/격리수준</a>과 함께 설계해야 효과가 납니다.</p>
<h3 id="4-캐시-적중률이-높아도-서비스-품질은-나쁠-수-있다">4) 캐시 적중률이 높아도 서비스 품질은 나쁠 수 있다</h3>
<p>히트율 95%인데 불만이 많은 서비스가 흔합니다. 이유는 &ldquo;틀린 데이터를 빠르게 전달&quot;하기 때문입니다. 그래서 최소 지표 세트가 필요합니다.</p>
<ul>
<li><code>cache_hit_ratio</code> (엔드포인트/테넌트별)</li>
<li><code>revalidation_304_ratio</code></li>
<li><code>stale_served_ratio</code></li>
<li><code>freshness_violation_rate</code> (SLO 초과 오래됨)</li>
<li><code>origin_qps_reduction</code></li>
</ul>
<p>즉 성능 지표 + 정확성 지표를 같이 봐야 합니다. 관측 설계는 <a href="/learning/deep-dive/deep-dive-observability-baseline/">Observability Baseline</a>과 <a href="/learning/deep-dive/deep-dive-observability-alarms/">알람 전략</a> 기준으로 묶는 편이 안전합니다.</p>
<h2 id="실무-적용">실무 적용</h2>
<h3 id="1-엔드포인트-분류와-기본-정책숫자조건우선순위">1) 엔드포인트 분류와 기본 정책(숫자·조건·우선순위)</h3>
<p>우선순위는 <strong>정확성 &gt; 원본 보호 &gt; 평균 응답속도</strong>로 둡니다.</p>
<ol>
<li>
<p>Tier A(결제/재고/주문 상태)</p>
<ul>
<li>기본: <code>max-age=0, must-revalidate</code></li>
<li>조건: 신선도 위반률 목표 0.1% 미만</li>
<li>CDN 캐시: 필요 시 키 좁게(사용자/권한 포함)</li>
</ul>
</li>
<li>
<p>Tier B(카탈로그/검색 결과 일부)</p>
<ul>
<li>기본: <code>max-age=15~60s</code>, <code>stale-while-revalidate=30~120s</code></li>
<li>조건: 원본 QPS 30% 이상 절감 목표</li>
<li>경고: stale 제공 비율이 20% 넘으면 TTL 재조정</li>
</ul>
</li>
<li>
<p>Tier C(문서/정적 메타)</p>
<ul>
<li>기본: 버전 해시 URL + <code>immutable</code></li>
<li>조건: 배포 시 캐시 무효화 비용 최소화</li>
</ul>
</li>
</ol>
<h3 id="2-3단계-롤아웃-플랜">2) 3단계 롤아웃 플랜</h3>
<p><strong>1주차: 측정 고정</strong></p>
<ul>
<li>주요 20개 API의 현재 헤더/TTL/히트율/오래됨 위반률 수집</li>
<li>캐시 키 설계 문서화(권한·언어·테넌트 분리 포함)</li>
</ul>
<p><strong>2주차: 정책 적용</strong></p>
<ul>
<li>Tier B/C부터 <code>s-maxage</code>, <code>stale-while-revalidate</code> 적용</li>
<li>ETag 생성 로직 통일(버전 필드 또는 콘텐츠 해시)</li>
<li>수정 API에 <code>If-Match</code> 검증 추가</li>
</ul>
<p><strong>3주차: 보호 자동화</strong></p>
<ul>
<li>원본 장애율 상승 시 <code>stale-if-error</code> 임시 확장(예: 60→300초)</li>
<li>신선도 위반률 초과 시 자동으로 TTL 축소</li>
<li>대시보드에 &ldquo;원본 보호 효과 vs 오래됨 비용&rdquo; 비교 차트 추가</li>
</ul>
<h3 id="3-의사결정-게이트-예시">3) 의사결정 게이트 예시</h3>
<ul>
<li>10분 이동창에서 <code>freshness_violation_rate &gt; 0.5%</code>면 즉시 TTL 50% 축소</li>
<li><code>origin_cpu &gt; 75%</code> + <code>tierB hit_ratio &lt; 70%</code>면 캐시 키 과분할 여부 점검</li>
<li><code>stale_served_ratio &gt; 30%</code> 30분 지속 시 revalidation 병목(ETag 계산, 조건부 GET) 우선 점검</li>
</ul>
<h2 id="트레이드오프주의점">트레이드오프/주의점</h2>
<ol>
<li>
<p><strong>TTL을 짧게 줄이면 정확성은 좋아지지만 원본 비용이 급증한다</strong><br>
특히 트래픽 피크 시간에 재검증 폭증이 생기면 DB/앱 서버가 먼저 포화됩니다.</p>
</li>
<li>
<p><strong>stale-while-revalidate는 UX를 살리지만 오류를 가릴 수 있다</strong><br>
장애를 숨겨 평균 응답속도는 좋아 보여도, 실제 데이터 지연은 커질 수 있습니다.</p>
</li>
<li>
<p><strong>ETag 계산이 무거우면 최적화가 역효과가 날 수 있다</strong><br>
대형 응답의 전체 해시를 매번 계산하면 CPU를 더 씁니다. 버전 기반 ETag가 가능한 구조를 우선 고려하세요.</p>
</li>
<li>
<p><strong>캐시 키 설계가 느슨하면 데이터 누출 사고로 이어질 수 있다</strong><br>
권한/테넌트/언어 분리가 빠지면 성능 문제가 아니라 보안 사고가 됩니다.</p>
</li>
</ol>
<h2 id="체크리스트-또는-연습">체크리스트 또는 연습</h2>
<h3 id="체크리스트">체크리스트</h3>
<ul>
<li><input disabled="" type="checkbox"> 엔드포인트를 데이터 위험도(Tier A/B/C)로 분류했다.</li>
<li><input disabled="" type="checkbox"> 각 Tier별 허용 오래됨(초)을 문서로 합의했다.</li>
<li><input disabled="" type="checkbox"> <code>Cache-Control</code> 정책에 <code>s-maxage</code>, <code>stale-*</code> 사용 여부가 명확하다.</li>
<li><input disabled="" type="checkbox"> ETag/If-Match를 읽기·쓰기 경로 모두에서 검토했다.</li>
<li><input disabled="" type="checkbox"> 히트율 외에 신선도 위반률/원본 보호 지표를 함께 모니터링한다.</li>
</ul>
<h3 id="연습-과제">연습 과제</h3>
<ol>
<li>현재 운영 중인 조회 API 5개를 골라 Tier를 분류하고, <code>max-age/s-maxage/stale-*</code> 초깃값을 제안해 보세요.</li>
<li>수정 API 1개를 골라 <code>If-Match</code> 기반 충돌 방지를 설계하고, 412 응답 시 클라이언트 재시도 UX를 정의해 보세요.</li>
<li>&ldquo;원본 CPU 80% 초과 시 stale-if-error를 5분까지 확장&rdquo; 같은 긴급 정책을 만들고, 해제 조건(예: CPU 60% 10분 유지)을 숫자로 정해보세요.</li>
</ol>
<h2 id="관련-글">관련 글</h2>
<ul>
<li><a href="/learning/deep-dive/deep-dive-http-essentials/">HTTP 기본기 정리</a></li>
<li><a href="/learning/deep-dive/deep-dive-http-deep-dive/">HTTP 심화: 헤더/캐시/커넥션 관점</a></li>
<li><a href="/learning/deep-dive/deep-dive-api-rate-limit-backpressure/">API Rate Limit &amp; Backpressure 설계</a></li>
<li><a href="/learning/deep-dive/deep-dive-slo-sli-error-budget/">SLO/SLI/Error Budget 운영</a></li>
<li><a href="/learning/deep-dive/deep-dive-observability-alarms/">Observability Alarms 실전</a></li>
</ul>
]]></content:encoded></item></channel></rss>