HTTP 캐시와 조건부 요청
웹 캐시와 조건부 요청
본 포스팅에서는 웹 애플리케이션 성능 향상의 핵심 요소인 캐시(Cache)와 조건부 요청(Conditional Request)을 다룹니다.
캐시의 기본 동작부터 검증 헤더(Verification Header), 조건부 요청, ETag, 그리고 프록시 캐시(Proxy Cache)와 캐시 무효화 전략까지 폭넓게 살펴봅니다.
이를 통해 웹에서 어떤 방식으로 네트워크 리소스를 절약하고 사용자 경험을 개선하는지 이해할 수 있을 것입니다.
1. 캐시 기본 동작
캐시가 없는 경우
- 클라이언트(브라우저)가
star.jpg
이미지를 서버에 요청한다고 가정합니다. - 서버는 HTTP 헤더(약 0.1MB)와 이미지 바디(약 1MB)를 합쳐 총 1.1MB를 전송합니다.
- 동일한 이미지를 다시 요청해도 캐시가 없다면 서버는 또다시 전체 1.1MB를 전송해야 하며, 이는 네트워크 대역폭 낭비와 느린 로딩 속도로 이어집니다.
캐시를 적용한 경우
- 서버는
Cache-Control: max-age=60
와 같이 캐시 유효기간을 설정할 수 있습니다(예: 60초). - 최초 요청 시, 서버는 이미지와 함께 캐시 가능한 응답을 내려주고, 브라우저는 이를 로컬 캐시에 저장합니다.
- 60초 이내의 재요청 시 브라우저는 네트워크를 거치지 않고 즉시 캐시에서 이미지를 가져와 훨씬 빠른 로딩을 제공합니다.
- 캐시 유효기간이 만료(예: 60초 초과)되면 브라우저는 다시 서버로 요청하여 최신 데이터를 받고, 캐시를 갱신합니다.
캐시 덕분에 네트워크 비용 감소, 응답 속도 개선, 사용자 경험 향상 등의 이점을 얻을 수 있습니다.
2. 검증 헤더와 조건부 요청 1 (Last-Modified 사용)
캐시 만료 후 데이터 변경 여부 확인 문제
- 캐시 유효시간이 초과되면 클라이언트는 다시 서버에 요청해야 합니다.
- 이때 서버 데이터가 변경되지 않았다면 굳이 1MB나 되는 바디를 재전송할 필요가 없을 것입니다.
- 이를 해결하기 위해 검증 헤더(Verification Header)와 조건부 요청(Conditional Request)을 사용합니다.
Last-Modified와 If-Modified-Since
- 서버는 응답 시
Last-Modified
헤더를 통해 리소스의 최종 수정 시간을 알려줄 수 있습니다. - 캐시는 해당 최종 수정일을 저장합니다.
- 캐시 만료 후, 클라이언트는
If-Modified-Since
헤더에 이 최종 수정일을 담아 재요청합니다. - 서버는 현재 리소스의 수정 시간을 확인하여 변경 여부를 판단합니다.
- 변경 없음 →
304 Not Modified
응답(바디 없이 헤더만 전송) - 변경 있음 →
200 OK
와 함께 새 데이터 전송
- 변경 없음 →
이로써 변경되지 않은 데이터에 대해 불필요한 대역폭 낭비를 줄이고, 클라이언트는 기존 캐시를 재활용할 수 있습니다.
3. 검증 헤더와 조건부 요청 2 (ETag 사용)
Last-Modified의 한계와 ETag 등장
Last-Modified
는 초 단위 이상의 정밀도 제한, 날짜 기반 변경 판정이라는 단점을 가질 수 있습니다.- 또한 실제 컨텐츠 변경 없이 파일 수정 시간만 갱신되는 경우에도 전체 데이터를 재다운로드해야 할 수 있습니다.
- 이를 보완하기 위해 ETag(Entity Tag)를 사용합니다.
- 서버는 리소스에 대해 임의의 고유 식별자(해시나 버전 번호)를
ETag
로 전송합니다. - 클라이언트는 캐시 만료 시
If-None-Match
헤더에 해당 ETag를 넣어 요청합니다. - 서버는 ETag를 비교하여 변경 여부를 판단:
- ETag 동일(변경 없음) →
304 Not Modified
(바디 없이 헤더만) - ETag 불일치(변경) →
200 OK
와 함께 새로운 데이터 전송
- ETag 동일(변경 없음) →
- 서버는 리소스에 대해 임의의 고유 식별자(해시나 버전 번호)를
ETag는 서버가 자유롭게 컨텐츠 변경 여부를 결정하고 제어할 수 있으며, 클라이언트는 단순히 ETag를 비교하는 것으로 캐시 유효성을 판단할 수 있습니다.
4. 캐시와 조건부 요청 관련 헤더 정리
캐시 제어 헤더
- Cache-Control:
max-age=[초]
: 캐시 유효시간 설정no-cache
: 캐시에 저장 가능하나, 사용 전 서버 검증 필수no-store
: 민감 정보 포함 시 저장 금지
- Pragma:
no-cache
(HTTP/1.0 하위 호환용) - Expires: 특정 만료일(날짜) 지정 (HTTP/1.0 구 방식)
→ 현대적 환경에서는max-age
사용 권장
검증 헤더와 조건부 요청 헤더
- 검증 헤더:
Last-Modified
: 리소스 최종 수정일ETag
: 리소스 고유 식별 태그
- 조건부 요청 헤더:
If-Modified-Since
:Last-Modified
와 함께 사용If-None-Match
:ETag
와 함께 사용- (추가)
If-Match
,If-Unmodified-Since
등도 존재
5. 프록시 캐시(Proxy Cache)
Proxy Cache의 개념
- 클라이언트와 원서버(Origin Server) 사이에 위치한 캐시 서버.
- 프록시 캐시 서버는 공용(퍼블릭) 캐시로서 여러 사용자 요청을 대리 처리하며, 한 번 가져온 데이터를 재활용하여 네트워크 지연과 트래픽 부담을 크게 줄일 수 있음.
예: 한국 클라이언트가 미국 서버에 접근할 경우, 중간에 한국 내 프록시 캐시 서버를 두어 응답 시간을 단축.
Public Cache vs Private Cache
- Public Cache: 여러 사용자들이 공통으로 활용 가능한 캐시.
- Private Cache: 브라우저 내부 캐시처럼 특정 사용자만 사용하는 캐시.
관련 캐시 지시어
public
: 응답이 퍼블릭 캐시에 저장 가능함을 의미.private
: 해당 응답은 사용자 전용으로, 퍼블릭 캐시에 저장 불가.
추가 지시어
s-maxage
: 프록시 캐시 전용 max-age 설정.Age
헤더: 프록시 캐시에 리소스가 머문 시간 정보 제공.
6. 캐시 무효화
왜 캐시 무효화가 필요한가?
어떤 페이지나 리소스는 절대 캐시되어서는 안 되거나, 항상 최신 상태를 반영해야 하는 경우가 있습니다.
예를 들어, 개인 금융 정보나 실시간 변화하는 데이터 등은 캐시 사용 시 문제를 야기할 수 있습니다.
캐시를 전혀 적용하지 않았다고 해서 브라우저가 임의로 캐시하지 않는 것은 아닙니다.
브라우저는 휴리스틱(추론)에 따라 캐시를 적용할 수 있기 때문에, 캐시를 확실히 막기 위한 명시적 지시어가 필요합니다.
확실한 캐시 무효화 방법
다음 헤더를 조합하면 확실히 캐시를 무효화할 수 있습니다.
Cache-Control: no-cache, no-store, must-revalidate
no-cache
: 항상 원서버 검증 필요no-store
: 데이터 저장 금지must-revalidate
: 캐시 만료 후 재검증 필수, 검증 불가 시 응답 불가능(504 등 오류)
Pragma: no-cache
(HTTP/1.0 호환)
이 조합을 통해 브라우저나 프록시 캐시가 데이터를 함부로 캐싱하거나 만료된 캐시를 재활용하지 않도록 강제할 수 있습니다.
특히 네트워크 문제로 원서버 검증이 불가능할 경우에도 과거 데이터 제공을 막음으로써, 민감한 정보의 무단 사용을 예방할 수 있습니다.
정리
- 캐시 활용: 불필요한 중복 다운로드를 줄이고, 응답 시간을 단축하며, 사용자 경험을 개선합니다.
- 조건부 요청(Last-Modified/ETag): 변경 사항이 없으면 304 응답으로 헤더만 받고, 캐시 재활용을 통해 대역폭 절약.
- 프록시 캐시: 지리적 거리 문제 해결, CDN 형태로 응답 개선.
- 캐시 무효화: 민감 정보나 항상 최신 상태가 필요한 페이지는 엄격한 헤더 설정으로 캐시를 막을 수 있음.
결과적으로, 적절한 캐시 전략과 조건부 요청, 검증 헤더를 조합하면 네트워크 리소스 절약과 최적화된 사용자 경험을 모두 달성할 수 있습니다.