HTTP 캐시 다루기(+Etag)

이희제·2025년 1월 7일
post-thumbnail

예전 회사 업무 중 CDN에 올라간 정적 파일(json, css)을 ETag 기반으로 캐시가 되도록 설정한 적이 있다.

이런 정적 파일에 대한 캐시를 잘 다루기 위해서는 Cache-Control 설정이 중요하다.

EtagCache-Control과 연관이 매우 깊다. Cache-Control에 따라 Etag 기반으로 동작할 수도 있고 안할 수도 있다.

그래서 먼저 Cache-Control에 대해 알아보자.

Cache-Control

Cache-Control은 HTTP 헤더 중 하나로, 클라이언트와 서버가 콘텐츠를 캐시하는 방식을 제어하는 데 사용된다.

해당 캐시를 효율적으로 활용하면서도 데이터의 최신성을 보장하기 위해, 캐싱 동작을 세부적으로 설
정할 수 있다.

Cache-Control 헤더는 요청(Request)와 응답(Response)에 포함될 수 있다. 이 헤더를 통해:

  1. 클라이언트의 요청이 캐시된 응답을 사용할 수 있는지 여부를 제어한다.
  2. 서버가 응답 데이터를 캐시하도록 허용할지, 허용한다면 어떤 조건으로 캐시할지를 지정한다.

더 많은 Cache-Control 디렉티브가 많지만(참고) 주요 디렉티브만 알아보자

max-age

max-age <seconds>: 캐시가 유효한 시간을 설정하는 값이다. 유효기간 전이라면 서버에 요청하지 않고 캐시를 읽어와서 사용한다.

no-cache와 no-store

그러면 가장 많이들 헷갈리는 no-cacheno-store에 대해 알아보자.

1. no-cache

no-cache는 언뜻보면 캐시를 하지 않을 것처럼 명칭이 되어 있다. 하지만 이는 max-age=0과 동일하고 캐시를 사용하되 사용할 때마다 서버에 재검증 요청을 보내야 한다.

request, response 헤더에 둘 다 사용될 수 있는데 차이가 있다.

response

응답 헤더에 설정이 되었다면, 앞으로 발생되는 요청에 대해서 검증 없이 캐싱을 하지 않겠다는 의미이다. 즉, 캐시를 사용하기 전에 서버의 검증이 필요하다는 말이다.

  • 예시: 서버가 JSON 데이터를 응답하면서 no-cache를 설정하면, 브라우저는 이 데이터를 캐시에 저장하지만, 다음 요청에서는 반드시 서버에 다시 가서 확인 후 사용한다.

더 쉽게 말하자면... 내가 준 물건(캐시 데이터)을 보관해도 되지만, 다음에 사용할 때 꼭 나한테 와서 확인받아라!" (서버가 검증 요청을 요구함)

request

요청 헤더에 해당 값이 설정되었다면, 검층 요청 없이는 캐시가 된 해당 요청에 대한 응답 값을 받지 않겠다는 의미이다.

즉 요청하는 쪽에서 서버에 "캐시된 데이터가 있더라도, 그걸 쓰기 전에 서버에 가서 이 데이터가 최신인지 확인해줘"라고 요청하는 것이다.

  • 예시: 웹 브라우저가 이미 저장한 이미지가 있지만, no-cache가 요청 헤더에 있으면 서버에 "이 이미지가 최신이 맞습니까?"라고 물어본 후 최신 상태라면 캐시된 이미지를 쓴다.

얘도 더 쉽게 말하자면 "내가 사용하려는 물건(캐시 데이터)이 문제없는지 꼭 주인(서버)에게 확인하고 써야겠다!" (클라이언트가 검증 요청함) 이다.

비슷하지만 미묘한 차이점이 있다... 결국 캐시를 사용할 때 검증을 받아야 하는 것은 동일하고 요청 주체만 다른 것이다. (누가 캐시 검증을 요구하냐 차이이다)

2. no-store

no-store가 이제 진짜 캐시를 절대 사용하지 않겠다고 할 때 사용할 수 있다.
Cache-Control: no-store를 사용하면 브라우저는 어떤 경우에도 캐시 저장소에 해당 리소스를 저장하지 않는다.

request, response 헤더에 둘 다 사용될 수 있고 사용될 때 의미는 동일하다. 클라이언트 및 서버에서 캐시를 아예 사용하지 않겠다는 설정이다.


Etag에 대해

Etag는 캐시가 유효하지 않을 때 재검증 요청을 보낼 때 사용된다.

대표적인 재검증 요청 헤더들로는 아래와 같이 2개의 헤더가 있다.

  • If-None-Match: 캐시된 리소스의 ETag 값과 현재 서버 리소스의 ETag 값이 같은지 확인한다.
  • If-Modified-Since: 캐시된 리소스의 Last-Modified 값 이후에 서버 리소스가 수정되었는지 확인한다.

브라우저는 다시 재검증 요청을 보낼 때 위의 ETag와 Last-Modified 값은 기존에 받았던 리소스의 응답 헤더에 있는 값을 사용한다.

참고로 두 헤더를 동시에 사용할 수 있다. MDN에 다음과 같이 나와있다.

이제 응답 헤더에 Cache-Control: no-cache가 설정되고 CDN에서 Etag를 지원한다고 가정해보자.

  1. 최초 요청시 응답 헤더에 대해 서버에서 Cache-Control 값을 no-cache로 내려준다. (캐시는 사용하지만 검층 요청을 보내겠다는 옵션)

  2. 서버에서 ETag 값을 내려준다.

  3. Cache-Control: no-cache로 설정되었기 때문에 클라이언트에서는 매번 받은 ETag 값을 If-None-Match에 넣어서 검증 요청을 보낸다.

  4. 서버는 클라이언트가 보낸 ETag와 현재 리소스의 ETag를 비교한다.

  • 일치(O): 리소스가 변경되지 않았음을 의미. 서버는 304 Not Modified 응답을 반환한다.
  • 불일치(X): 리소스가 변경되었음을 의미. 서버는 새 리소스와 상태 코드 200 OK를 반환한다.

내가 설정한 Cache-Control (With CDN)

회사에서 정적 파일인 json, css 파일이 CDN에 올라가 있는데 여러 서비스에서 해당 파일을 참조해서 사용하고 있다.

이전에는 Cache-Control: no-store로 설정이 되어 있었는데 비효율적이라 판단했다.

정적 파일이긴 하지만 꽤 자주 변경이 되어 CDN에 업로드가 된다. 그리고 해당 파일을 참조하고 있는 서비스들이 많기 때문에 변경 사항이 즉각적으로 반영이 되어야 한다.

그래서 나는 CDN에서 응답을 내려줄 때 응답 헤더에 Cache-Control: no-cache로 설정을 하여 일단 캐시를 사용하되 서버에 재검증 요청을 하도록 했다.

이때 재검증 요청은 위에서 설명한 것처럼 Etag 기반으로 진행이 된다.

내가 이전에 테스트한 결과를 확인해보면 다음과 같다.

1. 최초 요청 → 200 OK와 함께 Etag와 Last-Modified가 내려옴

응답 헤더에 Cache-Control: no-cache가 설정되어 있다.

2. 최초 요청 후 다시 요청 → 304 Not Modified (Etag 기반으로 정상적인 검증 확인 가능)

재요청 시에 요청 헤더 변조를 통해 If-None-Match만 포함해서 보냈다.

profile
그냥 하자

0개의 댓글