캐시(Cache)
캐시(Cache)
현대의 웹 서비스 환경에서는 캐시(Cache) 라는 기술을 통해 대규모 트래픽과 반복 요청을 효율적으로 처리한다. 캐시는 자원의 사본을 임시로 저장하고, 동일 자원에 대한 요청이 재발생할 때 사본을 재활용함으로써 대역폭 낭비를 줄이고 응답 속도를 높여준다. 이 글에서는 캐시의 기본 개념부터 캐시 유효 기간 설정, 재검사 로직, 그리고 확장된 상세 내용까지 단계적으로 살펴본다.
1. 캐시의 기본 개념
1.1 캐시(Cache)란?
HTTP 요청과 응답이 오가는 과정에서, 동일 자원(예: 이미지, HTML, CSS, JS 파일 등)에 대한 요청이 반복 발생한다. 캐시가 없다면 서버는 요청 때마다 동일한 자원을 전송하게 되고, 이는 네트워크 대역폭과 서버 자원을 과도하게 소모시킨다. 캐시를 통해 자원의 사본을 저장해두면 이후 동일 자원 요청에 대해서는 사본으로 빠르게 응답할 수 있다.
가령, 클라이언트가 서버로부터 10MB 크기의 이미지를 처음 수신했다면, 다음 요청부터는 캐시된 사본을 재활용하여 네트워크 사용량을 대폭 절감하고, 사용자 입장에서는 더 빠른 응답을 기대할 수 있다.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
1.2 개인 전용 캐시(Private Cache) vs. 공용 캐시(Public Cache)
캐시는 보통 웹 브라우저를 비롯한 클라이언트 측에 저장되기도 하고, 클라이언트와 서버 사이에 존재하는 중간 서버(프록시 서버 등) 에 저장되기도 한다.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
- 개인 전용 캐시(Private Cache)
웹 브라우저마다 별도의 캐시 영역을 사용한다. 개인 단위로 사용하는 캐시이므로, 민감 정보나 사용자별 인증된 자원 등을 안전하게 저장하기에 적합하다. - 공용 캐시(Public Cache)
다수의 사용자가 공유하는 중간 캐시로, 주로 프록시 서버나 CDN(Contents Delivery Network) 등이 해당한다. 여러 사람이 같은 자원에 접근할 때, 서버가 아닌 공용 캐시에서 빠르게 제공함으로써 효율을 극대화한다.
2. 캐시 활용 시 주의해야 할 점
캐시는 엄연히 ‘원본의 사본’ 이다. 원본 데이터가 언제 변경될지 모르므로, 캐시가 유효 기간 내에 있어도 원본이 변하면 캐시가 구버전이 될 수 있다. 이러한 문제를 방지하기 위해 정교한 캐시 전략과 재검사 로직이 필요하다.
2.1 Cache Hit vs. Cache Miss
- Cache Hit
요청한 자원이 캐시에 이미 존재하여, 서버가 아닌 캐시에서 자원을 즉시 제공하는 상황. - Cache Miss
요청한 자원을 캐시에서 찾지 못해 서버로부터 직접 받아와야 하는 상황. 새로운 자원을 수신한 뒤 캐시에 저장하면, 이후 동일 자원 요청 시에는 Cache Hit이 발생할 가능성이 높아진다.
2.2 캐시 오염(Cache Pollution)과 불일치
- 캐시 오염: 캐시된 데이터가 악의적으로 변형되거나, 의도치 않게 잘못된 데이터가 캐시에 저장된 경우를 의미한다.
- 불일치 문제: 원본이 수정되었는데, 캐시가 갱신되지 않아 오래된 데이터를 계속 반환하게 되는 상황.
3. 캐시 신선도(Cache Freshness)
캐시된 데이터가 최신 원본과 얼마나 동일한지를 캐시 신선도(Cache Freshness) 라고 부른다. 캐시 신선도가 높아야 정확한 데이터를 반환할 수 있고, 그렇지 않다면 캐시로 인한 이점보다 데이터 무결성 문제가 더 커질 수 있다.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
4. 캐시 유효 기간 설정
4.1 Expires 헤더와 Cache-Control 헤더
캐시 신선도를 유지하는 가장 기본적인 방법은 유효 기간을 명시하는 것이다.
Expires
헤더
날짜와 시각을 직접 기술하여 만료 시점을 지정한다. 예)Expires: Tue, 06 Feb 2024 12:00:00 GMT
Cache-Control: max-age
초 단위로 만료 시간을 지정한다. 예)Cache-Control: max-age=1200
(1200초, 즉 20분 동안 유효)
일반적으로 HTTP/1.1 이상에서는 Cache-Control
헤더가 우선순위가 높다. 또한 Cache-Control
에는 더 세부적인 지시자(directive)를 부여할 수 있다. 예를 들어:
Cache-Control: no-cache
: 매번 원본 서버에 신선도 재검사를 수행해야 함.Cache-Control: no-store
: 아예 캐시에 저장하지 않음.Cache-Control: private
: 개인 전용 캐시에만 저장 가능.Cache-Control: public
: 공용 캐시에도 저장 가능.must-revalidate
: 만료된 경우, 반드시 서버에 재검사를 요청해야 함.s-maxage
: 공유(공용) 캐시에만 적용되는 만료 시간 설정.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
5. 캐시 유효 기간 만료 후 재검사(Validation)
유효 기간이 만료된 캐시에 대해서는 원본 서버와의 재검사를 통해 여전히 최신 자원인지 확인할 수 있다. 만약 원본이 변하지 않았다면, 새 자원을 전송할 필요 없이 304 Not Modified 상태 코드만으로도 충분하다.
5.1 날짜 기반 재검사 (If-Modified-Since)
If-Modified-Since
헤더에는 특정 시점이 들어가며, 그 시점 이후로 자원이 변경되었다면 새 자원을 달라는 뜻을 서버에 전달한다.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
예시 요청 메시지:
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
서버의 응답 시나리오:
- 자원이 변경되었음
상태 코드200 OK
와 함께 새 자원 반환 - 자원이 변경되지 않았음
상태 코드304 Not Modified
(본문 없음) - 자원이 삭제되었음
상태 코드404 Not Found
서버는 또한 Last-Modified
헤더를 통해 자원 최종 수정 시점을 알려줄 수 있다.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
5.2 엔티티 태그(Etag) 기반 재검사 (If-None-Match)
엔티티 태그(Entity Tag, Etag) 는 자원의 버전 정보를 식별하기 위한 값이다. 원본이 변동될 때마다 Etag도 새로운 값으로 바뀌며, 의미 있는 변경이 없으면 Etag도 유지된다.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
클라이언트는 If-None-Match
헤더에 캐싱된 Etag를 포함하여, 서버에 해당 Etag와 다른 버전이 존재하는지 묻는다.
(그림 출처: 혼자 공부하는 컴퓨터 네트워크)
서버의 응답 시나리오:
- 자원이 변경되었음(Etag 값 변경)
상태 코드200 OK
와 함께 새로운 자원 + Etag 반환 - 자원이 변경되지 않았음(Etag 값 동일)
상태 코드304 Not Modified
반환 - 자원이 삭제되었음
상태 코드404 Not Found
Strong Etag vs. Weak Etag
- Strong Etag: 자원이 한 비트라도 달라지면 새로운 Etag를 생성한다.
- Weak Etag: 자원의 일부만 바뀌어도 꼭 새로운 Etag를 생성하지 않을 수 있으며, 보다 덜 민감하게 버전을 판단한다. 컨텐츠가 동일한 의미를 지니면 같은 Etag를 사용할 수 있다.
6. 참고 자료
- MDN Web Docs: HTTP Caching
- RFC 7234 - HTTP/1.1 Caching
- RFC 7232 - HTTP/1.1 Conditional Requests
- RFC 7231 - HTTP/1.1 Semantics and Content
캐시 활용은 네트워크 트래픽을 줄이고, 응답 속도를 높여주며, 서비스 효율성을 전반적으로 개선한다. 그러나 원본 데이터 변경 시 적시에 캐시 무효화가 일어나지 않으면 심각한 문제를 야기할 수 있으므로, 적절한 캐시 제어 헤더와 재검사 로직을 설정하는 것이 핵심이다.