HTTP 캐싱 완벽 가이드: ETag, Cache-Control, CDN
웹사이트가 느리게 느껴지는 이유 (그리고 캐싱이 해결하는 방법)
이미지를 최적화하고, CSS를 압축하고, 서버까지 업그레이드했습니다. 그런데도 재방문 사용자는 여전히 느린 로딩을 경험하고, 오리진 서버는 대역폭을 낭비하고 있습니다. 원인은? 비효율적인 HTTP 캐싱입니다. 적절한 캐싱 헤더가 없으면 브라우저는 방문할 때마다 동일한 자산을 다시 다운로드하고, CDN은 제 역할을 하지 못합니다.
HTTP 캐싱은 가장 효과가 큰 성능 최적화 중 하나입니다. 지연 시간을 줄이고, 대역폭 비용을 절감하며, 오리진 서버의 부하를 줄입니다. 이 가이드에서는 핵심 메커니즘인 ETag, Cache-Control, 그리고 CDN이 어떻게 관련되는지 살펴봅니다. 올바르게 구현하는 실전 전략도 배우게 됩니다.
HTTP 캐싱 작동 방식: 전체 그림
브라우저가 리소스를 요청할 때, 오리진 서버에서 가져오거나 로컬에 저장된 복사본을 사용할 수 있습니다. HTTP 캐싱은 저장된 복사본이 언제 신선한지, 언제 재검증해야 하는지에 대한 규칙을 정의합니다.
크게 두 가지 유형의 캐싱이 있습니다:
- 브라우저 캐싱(프라이빗): 사용자의 브라우저가 리소스를 로컬에 저장합니다. 단일 사용자가 여러 페이지나 방문에 걸쳐 이득을 봅니다.
- 공유 캐싱(CDN, 프록시): 중간 서버가 여러 사용자를 위해 리소스를 캐시합니다. 오리진 부하를 줄이고 전 세계적으로 전송 속도를 높입니다.
둘 다 신선도와 재검증을 결정하기 위해 HTTP 헤더에 의존합니다. 가장 중요한 두 헤더는 Cache-Control과 ETag입니다.
Cache-Control: 신선도 규칙
Cache-Control은 캐싱 정책을 정의하는 기본 헤더입니다. 캐시에 응답을 어떻게 처리할지 알려주는 지시문 기반 헤더입니다.
주요 지시문
- max-age: 응답이 신선한 것으로 간주되는 시간(초)입니다. 예를 들어
Cache-Control: max-age=3600은 응답이 1시간 동안 신선함을 의미합니다. - s-maxage: max-age와 유사하지만 공유 캐시(CDN) 전용입니다. 공유 캐시에서 max-age를 재정의합니다.
- public: 응답을 모든 캐시(공유 캐시 포함)에서 캐시할 수 있습니다.
- private: 응답이 단일 사용자용이며 공유 캐시에 저장되어서는 안 됩니다.
- no-cache: 응답을 저장할 수 있지만, 사용 전에 매번 오리진과 재검증해야 합니다.
- no-store: 응답을 어떤 캐시에도 저장해서는 안 됩니다. 민감한 데이터에 사용하세요.
- must-revalidate: 만료되면 캐시는 재검증 없이 응답을 사용해서는 안 됩니다.
- immutable: 응답이 신선도 수명 동안 변경되지 않습니다. 버전이 지정된 자산에 유용합니다.
예: Cache-Control: public, max-age=31536000, immutable은 해시된 파일 이름을 가진 정적 자산에 이상적입니다.
ETag와 조건부 요청
ETag(Entity Tag)는 리소스의 특정 버전에 대한 식별자입니다. 리소스가 변경되면 ETag도 변경됩니다. 브라우저는 ETag를 사용하여 조건부 요청을 합니다: 저장된 ETag를 If-None-Match 헤더에 담아 보냅니다. 리소스가 변경되지 않았다면 서버는 304 Not Modified와 본문 없이 응답하여 대역폭을 절약합니다.
마찬가지로 Last-Modified는 If-Modified-Since와 함께 작동하지만, ETag가 더 정밀합니다(같은 초 내의 변경도 감지할 수 있음).
ETag 생성 방법
대부분의 웹 서버와 프레임워크는 ETag를 자동으로 생성합니다. 예를 들어 Express.js에서는 app.set('etag', 'strong')으로 활성화할 수 있습니다. Nginx에서는 정적 파일에 대해 ETag가 기본으로 켜져 있습니다.
강한 ETag(예: "abc123")는 바이트 단위 동일성을 보장합니다. 약한 ETag(예: W/"abc123")는 정확한 바이트가 아닌 의미적 동등성을 나타냅니다.
CDN 캐싱: 대규모 공유 캐시
CDN(Content Delivery Network)은 전 세계에 분산된 공유 캐시 역할을 합니다. 엣지 위치에 콘텐츠를 캐시하여 가까운 접속 지점에서 사용자에게 제공합니다. 이는 지연 시간을 줄이고 오리진 부하를 분산합니다.
CDN은 Cache-Control 헤더를 존중하지만, 자체 구성이 있는 경우가 많습니다. 주요 개념:
- 엣지 캐시: CDN의 로컬 캐시입니다. 캐시 키(보통 URL +
Accept-Encoding같은 헤더)를 기반으로 응답을 저장합니다. - 오리진 실드: 오리진으로 가는 요청을 줄이는 추가 캐싱 계층입니다.
- 캐시 무효화: CDN은 리소스를 업데이트할 때 캐시된 콘텐츠를 제거하는 API를 제공합니다.
CDN을 사용할 때는 s-maxage와 함께 Cache-Control을 설정하여 브라우저 캐시와 별도로 공유 캐시 신선도를 제어하세요. 예: Cache-Control: public, max-age=600, s-maxage=3600은 브라우저는 10분, CDN은 1시간 동안 캐시함을 의미합니다.
캐싱 헤더 비교
| 헤더 | 목적 | 예시 |
|---|---|---|
Cache-Control |
신선도 및 캐싱 규칙 정의 | public, max-age=3600 |
ETag |
리소스 버전의 고유 식별자 | "abc123" |
Last-Modified |
마지막 수정 타임스탬프 | Wed, 21 Oct 2025 07:28:00 GMT |
Expires |
레거시 절대 만료 날짜 | Wed, 21 Oct 2025 07:28:00 GMT |
Vary |
캐싱에 영향을 주는 헤더 지정 | Accept-Encoding |
참고: Expires는 Cache-Control로 대체되었지만, 구형 클라이언트에서 여전히 사용됩니다.
웹 앱을 위한 실전 캐싱 전략
효과적인 캐싱을 구현하려면 다음 단계를 따르세요:
- 정적 자산에 핑거프린트 적용: 해시된 파일 이름(예:
app.a1b2c3.js)을 사용하고immutable과 함께 긴max-age를 설정하세요. 파일이 변경되면 해시가 바뀌어 캐시가 무효화됩니다. - HTML에 적절한 Cache-Control 설정: HTML은 보통
no-cache또는 짧은max-age로 설정하여 사용자가 빠르게 업데이트를 받도록 하세요. 재검증에는ETag를 사용하세요. Vary: Accept-Encoding사용: 압축 버전과 비압축 버전을 제공하는 경우, 캐시가 이를 별도로 저장하도록 보장합니다.- s-maxage로 CDN 활용: 공유 캐시에는 더 긴
s-maxage를 설정하여 오리진 부하를 줄이고, 필요하면 브라우저 캐시는 더 짧게 유지하세요. - 현명하게 무효화: 중요한 업데이트를 배포할 때 CDN 퍼지 API를 사용하세요. 정적 자산은 핑거프린팅으로 퍼지 필요성을 피할 수 있습니다.
- 캐시 적중률 모니터링: CDN 분석을 사용하여 캐시가 효과적인지 확인하세요. 낮은 적중률은 헤더 구성 오류를 의미합니다.
흔한 실수와 예방법
- HTML 과다 캐싱: 사용자가 오래된 콘텐츠를 봅니다.
no-cache또는 짧은max-age를 사용하세요. - 정적 자산 과소 캐싱: 핑거프린팅과 함께 긴
max-age(예: 1년)를 설정하세요. Vary무시: 캐시가 잘못된 콘텐츠(예: gzip vs. 일반)를 제공할 수 있습니다. 항상Vary: Accept-Encoding을 설정하세요.- 사용자별 데이터에
private누락: 공유 캐시가 데이터를 유출할 수 있습니다. 인증된 응답에는Cache-Control: private을 사용하세요. no-store오용: 모든 캐싱을 방지하여 성능을 저하시킬 수 있습니다. 민감한 데이터에만 사용하세요.
캐싱 설정 테스트
브라우저 DevTools(네트워크 탭)를 사용하여 응답 헤더를 검사하고 리소스가 캐시에서 제공되는지 확인하세요("(from disk cache)" 또는 "(from memory cache)" 확인). CDN 동작은 curl -I로 X-Cache 또는 CF-Cache-Status 같은 헤더를 확인하세요. WebPageTest 같은 도구는 여러 방문에 걸친 캐싱을 시각화할 수 있습니다.
FAQ
ETag와 Last-Modified의 차이점은 무엇인가요?
ETag는 리소스가 변경될 때 바뀌는 불투명한 식별자이고, Last-Modified는 타임스탬프입니다. ETag는 같은 초 내의 변경도 감지할 수 있고 시계 동기화에 의존하지 않아 더 정밀합니다.
no-cache와 no-store는 언제 사용해야 하나요?
캐시가 응답을 저장하되 사용 전에 매번 오리진과 재검증하길 원할 때 no-cache를 사용하세요. 어떤 캐시도 디스크나 메모리에 기록해서는 안 되는 민감한 데이터에는 no-store를 사용하세요.
CDN은 캐시 무효화를 어떻게 처리하나요?
CDN은 특정 URL이나 전체 디렉터리를 엣지 캐시에서 제거할 수 있는 퍼지 API를 제공합니다. 일부는 콘텐츠를 만료로 표시하고 다음 요청 시 재검증하는 소프트 퍼지도 지원합니다. 자산 핑거프린팅이 퍼지보다 더 효율적인 경우가 많습니다.
결론
ETag, Cache-Control, CDN을 활용한 HTTP 캐싱을 마스터하는 것은 빠르고 확장 가능한 웹 애플리케이션을 구축하는 데 필수적입니다. 정적 자산과 HTML에 적절한 헤더를 설정하는 것부터 시작하고, s-maxage로 CDN 공유 캐시를 활용하며, 항상 구성을 테스트하세요. 캐싱 헤더의 작은 변화가 상당한 성능 향상을 가져올 수 있습니다.
서버 로그를 빠르게 분석하여 캐시 적중률을 확인해야 하나요? Nginx Log Analyzer로 액세스 로그를 파싱하고 시각화해 보세요.