HTTP 캐시 헤더
HTTP 캐시 헤더 (HTTP Cache Headers)
1. 개요
HTTP 캐싱이란 웹 브라우저나 중간 프록시 서버가 서버로부터 받은 응답 데이터를 일정 기간 저장해 두었다가, 동일한 요청이 발생했을 때 서버에 다시 요청하지 않고 저장된 복사본을 사용하여 응답하는 기술이다.
1.1 목적
- 응답 속도 향상: 네트워크 왕복 시간(Round Trip Time, RTT)을 줄여 사용자에게 더 빠른 콘텐츠 제공이 가능하다.
- 서버 부하 감소: 동일한 리소스에 대한 반복적인 요청을 서버가 처리하지 않아도 되므로 CPU 및 메모리 자원을 절약할 수 있다.
- 대역폭 절약: 데이터 전송량을 줄여 네트워크 비용을 절감하고 트래픽 혼잡을 방지한다.
1.2 캐시의 종류
- 브라우저 캐시 (Private Cache): 개별 사용자의 로컬 기기에 저장되는 캐시이다. 해당 사용자만 접근 가능하며,
<a href="/doc/%EA%B8%B0%EC%88%A0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/HTTP%20%ED%97%A4%EB%8D%94/Cache-Control" class="wiki-link wiki-link-missing">Cache-Control</a>: private설정 시 이곳에만 저장된다. - 공유 캐시 (Shared Cache): CDN(Content Delivery Network)이나 프록시 서버와 같이 여러 사용자가 공유하는 캐시이다.
Cache-Control: public설정 시 저장될 수 있으며, 서버와 클라이언트 사이의 물리적 거리를 줄이는 역할을 한다.
2. 신선도 검사 (Freshness: 유효 기간 설정)
신선도 검사는 캐시된 리소스가 여전히 유효한지(Fresh), 아니면 만료되었는지(Stale)를 판단하는 과정이다.
Cache-Control 헤더
HTTP/1.1의 표준 캐시 제어 헤더로, <a href="/doc/%EA%B8%B0%EC%88%A0/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/HTTP%20%ED%97%A4%EB%8D%94/Expires" class="wiki-link wiki-link-missing">Expires</a> 헤더보다 우선순위가 높으며 더 세밀한 제어가 가능하다.
| 지시어 | 설명 | 동작 방식 및 특징 |
|---|---|---|
max-age=<seconds> |
리소스의 최대 유효 기간 설정 | 설정된 초(seconds) 동안 리소스를 '신선한' 상태로 간주함 |
no-cache |
캐시는 저장하지만, 사용 전 반드시 재검증 수행 | 캐시 저장소에 저장하되, 사용 전 서버에 변경 여부를 확인해야 함 (no-store와 다름) |
no-store |
어떤 상황에서도 캐싱 금지 | 보안이 중요한 민감 정보의 경우 사용하며, 로컬/공유 캐시 모두에 절대 저장하지 않음 |
public |
모든 캐시(공유 캐시 포함)에 저장 가능 | 중간 프록시나 CDN에서도 캐싱할 수 있도록 허용함 |
private |
브라우저(개인 캐시)에만 저장 가능 | 공유 캐시에는 저장되지 않으며, 특정 사용자 전용 리소스에 사용함 |
must-revalidate |
만료 후 엄격한 재검증 강제 | 캐시가 만료된 후 네트워크 연결이 끊긴 상황에서도 오래된 데이터를 반환하지 않고 반드시 서버 확인을 거쳐야 함 |
stale-while-revalidate |
만료 후 일정 기간 동안 오래된 데이터 제공 | 캐시가 만료되었더라도 설정된 시간 동안은 일단 오래된 데이터를 반환하고, 백그라운드에서 최신 데이터를 갱신함 |
Expires 헤더
HTTP/1.0에서 사용되던 헤더로, 리소스가 만료되는 절대적인 날짜와 시간을 지정한다. (예: Expires: Wed, 21 Oct 2025 07:28:00 GMT)
* 한계: 서버와 클라이언트의 시스템 시간이 일치하지 않을 경우 오작동할 가능성이 크다. 따라서 현재는 상대적 시간을 사용하는 Cache-Control: max-age가 권장된다.
3. 재검증 (Validation: 변경 사항 확인)
캐시의 유효 기간이 만료되었을 때, 리소스를 새로 다운로드하는 대신 서버에 "내용이 변경되었는가?"를 확인하는 과정이다.
ETag와 Last-Modified
서버는 리소스의 상태를 나타내는 식별자를 헤더에 담아 보낸다.
- ETag (Entity Tag): 리소스의 내용을 기반으로 생성한 고유한 해시값이다. 내용이 조금이라도 변경되면 ETag 값이 바뀐다.
- Last-Modified: 리소스가 마지막으로 수정된 시각을 기록한다.
재검증 흐름과 304 Not Modified
- 클라이언트가 만료된 캐시를 발견하면,
If-None-Match(ETag 확인) 또는If-Modified-Since(수정 시각 확인) 헤더를 담아 요청을 보낸다. - 서버는 현재 리소스의 상태와 요청 헤더의 값을 비교한다.
- 변경 사항이 없다면: 서버는 본문(Body) 없이
304 Not Modified상태 코드만 반환한다. - 변경 사항이 있다면: 서버는 새로운 리소스와 함께
200 OK상태 코드를 반환한다.
| 비교 항목 | ETag | Last-Modified |
|---|---|---|
| 판단 기준 | 콘텐츠의 고유 해시값 (내용 중심) | 파일의 최종 수정 시간 (시간 중심) |
| 정밀도 | 매우 높음 (내용이 같으면 시간 달라도 동일 간주) | 낮음 (1초 미만의 변경 감지 불가) |
| 오버헤드 | 해시 생성 비용 발생 | 파일 시스템의 메타데이터만 확인하여 가벼움 |
4. 캐시 제어 전략 (Caching Strategies)
정적 자원 (Strong Caching)
이미지, JS, CSS 파일처럼 내용이 거의 변하지 않는 자원은 매우 긴 유효 기간을 설정한다.
* 전략: Cache-Control: public, max-age=31536000, immutable
* immutable 지시어: 리소스가 절대 변경되지 않음을 나타낸다. 이 설정이 있으면 사용자가 브라우저에서 새로고침(F5)을 눌러도 서버에 재검증 요청(Conditional Request)을 보내지 않고 즉시 캐시를 사용하도록 강제하여 성능을 극대화한다.
* 특징: 한 번 다운로드하면 1년 동안 서버에 묻지 않고 캐시를 사용한다. 변경이 필요할 때는 '캐시 무효화' 기법을 병행한다.
동적 데이터 (Revalidation Caching)
API 응답이나 자주 변경되는 HTML 페이지는 항상 최신 상태를 유지해야 한다.
* 전략: Cache-Control: no-cache 또는 Cache-Control: max-age=0, must-revalidate
* 특징: 캐시는 저장하지만, 매번 서버에 재검증 요청을 보내 변경 여부를 확인한 뒤 사용한다.
Vary 헤더의 활용
Vary 헤더는 서버가 응답을 보낼 때, 어떤 요청 헤더를 기준으로 캐시를 구분해야 하는지 알려주는 지시어이다.
* 역할: 동일한 URL이라도 요청 헤더에 따라 다른 응답을 보내야 할 때, 캐시 저장소에 여러 버전의 응답을 저장하도록 하여 잘못된 데이터가 전달되는 것을 방지한다.
* 상세 예시: Vary: Accept-Encoding
* 상황: 서버가 Gzip 압축을 지원하는 브라우저에는 압축된 파일을, 지원하지 않는 브라우저에는 일반 파일을 보낸다.
* 문제: 만약 Vary 헤더가 없다면, 공유 캐시(CDN)가 압축된 파일을 저장한 후, 압축을 지원하지 않는 브라우저에게도 압축된 파일을 그대로 전달하여 페이지가 깨질 수 있다.
* 해결: Vary: Accept-Encoding을 설정하면 캐시는 Accept-Encoding 헤더 값에 따라 각각 별도의 캐시 복사본을 유지한다.
5. 캐시 무효화 (Cache Busting)
강한 캐싱(max-age가 긴 경우)을 적용한 리소스를 서버에서 수정했을 때, 클라이언트가 강제로 새 버전을 받게 하는 기법이다.
- 버전 관리 (Versioning/Hashing): 파일 이름에 해시값이나 버전 번호를 포함시킨다.
- 예:
style.css→style.a1b2c3d.css - 파일 내용이 바뀌면 파일 이름 자체가 바뀌므로, 브라우저는 이를 완전히 새로운 리소스로 인식하여 다시 다운로드한다. (가장 권장되는 방식)
- 예:
- 쿼리 스트링 (Query String): URL 뒤에 버전 파라미터를 붙인다.
- 예:
style.css?v=1.2 - 구현이 간단하지만, 일부 프록시 서버가 쿼리 스트링이 포함된 URL을 캐싱하지 않는 경우가 있어 주의가 필요하다.
- 예:
6. 요약 및 주의사항
캐시 흐름 시퀀스 다이어그램
sequenceDiagram
participant Client as 브라우저
participant Cache as 로컬/공유 캐시
participant Server as 웹 서버
Client->>Cache: 리소스 요청
alt 캐시 존재 & 신선함 (Fresh)
Cache-->>Client: 200 OK (from cache)
else 캐시 존재 & 만료됨 (Stale)
Cache->>Server: 재검증 요청 (If-None-Match/If-Modified-Since)
alt 변경 사항 없음
Server-->>Cache: 304 Not Modified
Cache-->>Client: 200 OK (from cache)
else 변경 사항 있음
Server-->>Cache: 200 OK (New Resource)
Cache-->>Client: 200 OK (New Resource)
end
else 캐시 없음
Cache->>Server: 리소스 요청
Server-->>Cache: 200 OK (Resource + Cache Headers)
Cache-->>Client: 200 OK
end
주의사항 및 보안 고려사항
- 민감 정보 캐싱 금지: 로그인 세션 정보, 개인정보가 포함된 API 응답은 반드시
Cache-Control: no-store를 설정하여 디스크에 저장되지 않도록 해야 한다. - 과도한 캐싱 주의:
max-age를 너무 길게 설정하고 캐시 무효화 전략을 세우지 않으면, 서버에서 파일을 수정해도 사용자는 계속 옛날 버전을 보게 되는 문제가 발생한다. - Vary 헤더 남용:
Vary헤더에 너무 많은 항목(예:User-Agent)을 넣으면 캐시 적중률(Cache Hit Rate)이 급격히 떨어져 캐시의 효율성이 사라진다.
분류: 기술 / 웹기술 / HTTP 캐싱
이 문서는 AI 모델(gemma-4-31b)에 의해 생성된 콘텐츠입니다.
주의사항: AI가 생성한 내용은 부정확하거나 편향된 정보를 포함할 수 있습니다. 중요한 결정을 내리기 전에 반드시 신뢰할 수 있는 출처를 통해 정보를 확인하시기 바랍니다.