HTTP 요청을 통해 데이터를 주고 받을 때 네트워크 통신 비용이 발생한다. 만약 데이터가 변경되지 않은 상태라면, 이전에 요청을 통해 받은 데이터를 클라이언트나 캐시 서버에 캐시해둬서 오리진 서버측에 직접 요청하지 않고 통신 비용을 아낄 수 있지 않을까?
HTTP에선 응답 데이터에 대해서 캐시를 어떻게 다룰 지에 대한 명세를 나타내는 헤더들이 존재한다. 이번 포스팅에선 이 헤더들에 대해 알아보겠다.
Cache-Control
Cache-Control 헤더는 캐시에 관련된 HTTP/1.1 부터 도입된 표준 헤더이다. 브라우저의 캐시 뿐만 아니라 프록시나 CDN에 대해서도 다룬다.
디렉티브라고 부르는 옵션들이 있고 콤마로 구분한다. 일반적으로 다음과 같은 옵션을 다룬다.
no-cache
Cache-Control: no-cache
no-cache 디렉티브는 캐시로부터 오래된 리소스가 반환되는 것을 막기 위해 사용된다.
클라이언트가 요청에 no-cache 디렉티브를 달아서 요청하면 중간 서버에서 캐시된 응답을 주지 않고 오리진 서버까지 요청을 전송해야 한다.
서버의 응답에서 no-cache가 사용된 경우는 중간 서버가 응답을 저장할 수 없음을 의미한다.
Cache-Control: no-cache=Location
서버의 응답으로 no-cache디렉티브 필드 값에 헤더 필드 명이 지정된 경우에는 해당 헤더만 캐시할 수 없음을 의미한다.
no-store
Cache-Control: no-store
캐시하지 않는다. 실시간성 데이터이거나, 요청 또는 응답에 기밀 정보가 있을 경우 사용한다고 한다.
max-age
Cache-Control: max-age=604800
이 디렉티브는 캐시의 유효기간을 초단위로 나타낸다. 응답을 받은 뒤 지정한 시간동안만 캐시하여 사용하고, 지정 시간이 지나면 서버로 다시 요청한다. 만약 304 Not Modified 응답을 받으면 캐싱된 응답의 캐싱 시간을 지정한 시간만큼 다시 연장한다.
매커니즘상 max-age=0 은 no-cache 라고 볼 수 있다.
HTTP/1.1 스펙의 캐시 서버에선 Expires 헤더와 함께 사용될 경우 max-age가 우선되고 Expires가 무시된다. 반대로 HTTP/1.0에선 Expires가 우선된다.
s-maxage
max-age 디렉티브와 동일한데, 다른 점은 여러 유저가 이용하는 공유 캐시 서버에만 저장된다는 것이다. s-maxage 디렉티브가 사용되면, Expires헤더와 max-age 디렉티브는 무시된다.
public
Cache-Control: public
public 디렉티브가 사용되면, 공유 캐시에 저장해도 된다는 의미이다. 단, Authorization 헤더가 있는 요청에 대한 응답은 공유 캐시에 저장하면 안된다. 주의할 것.
private
Cache-Control: private
응답이 개인 캐시에만 저장될 수 있음을 의미한다. 캐시 서버에서 개인을 위해 응답을 캐시할 수는 있지만, 다른 사용자에겐 그 캐시를 반환하지 않도록 해야한다.
must-revalidate
Cache-Control: must-revalidate
캐시 만료 후 최초 조회시 오리진 서버에 검증하도록 한다. 오리진 서버에 접근 실패시 반드시 504 Gateway Timeout이 발생한다.
요청시 no-cache 디렉티브 덕에 오리진 서버에서 데이터를 요청하려 했는데, 오리진 서버에 접근이 불가능한 경우 오류보단 캐시된 데이터라도 보여주도록 한다. 하지만, must-revalidate가 붙어있다면, 원 서버에 접근이 불가능한 경우 항상 오류가 발생한다. 주로 중요한 데이터의 경우 사용
Last-Modified
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT
해당 데이터의 최종 수정일을 나타낸다. Cache-Control 헤더에서 max-age 디렉티브의 시간이 지났을 때 해당 캐시가 아직도 유효한 지 확인하기 위해 함께 캐싱된다.
If-Modified-Since
If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT
Cache-Control 의 max-age의 시간이 지났다면, 클라이언트는 If-Modified-Since 헤더 필드에 캐시해둔 Last-Modified의 값을 담아서 캐시 서버에 최근 캐시가 언제 바뀌었는 지 확인한다. 캐시 서버는 자기 캐시에 Last-Modified와 비교하여 아직 유효하다면, 바디 없이 304 Not Modified를 응답한다. 클라이언트는 서버가 보낸 응답 헤더 정보로 캐시를 갱신하고 캐시에 저장되어있는 데이터를 재활용한다.
Last-Modified, If-Modified-Since 단점
- 1초 미만(0.x초) 단위로 캐시 조정이 불가능
- 날짜 기반의 로직 사용
- 데이터를 수정해서 날짜가 다르지만, 같은 데이터를 다시 이전 데이터로 수정해서 데이터 결과가 똑같은 경우
- 서버에서 별도의 캐시 로직을 관리하고 싶은 경우
- ex) 스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지하고 싶은 경우
ETag
위 단점을 보완하기 위한 ETag헤더가 있다.
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
엔티티 태그라고 불리며 리소스를 특정하기 위한 문자열을 전달한다. 서버는 리소스마다 ETag값을 할당한다. ETag값의 문자에는 특별한 룰이 정해져 있지 않고 서버에서 임의로 할당한다.
- 데이터마다 고유한 문자열을 할당함 (주로 Hash?)
- 데이터가 변경되면 문자열도 변경
- 단순하게 ETag만 보내서 같으면 유지, 다르면 다시 받음
If-None-Match
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified와 If-Modified-Since가 세트라면, ETag는 If-None-Match와 세트이다.
캐시 서버에서 캐시해둔 응답의 ETag와 비교해서 다르면 오리진 서버까지 요청을 전달하고 같은 ETag면 캐시해둔 응답을 사용한다.
ETag와 If-None-Match를 사용하는 캐시 메커니즘은 캐시 제어 로직을 서버에서 완전히 관리할 수 있다.
ex) 서버는 배타 오픈 기간인 3일동안 파일이 변경되어도 ETag를 동일하게 유지, 애플리케이션 배포 주기에 맞춰서 ETag를 모두 갱신
Age
Age: 24
오리진 서버에서 응답 후 캐시된 시간(초)
확실한 캐시 무효화 응답
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache # HTTP 1.0 하위 호환
'Web' 카테고리의 다른 글
리프레시 토큰 탈취 대응 전략을 고민하다 도달한 세션쿠키 vs 토큰에 대한 고민 (0) | 2022.11.09 |
---|---|
OAuth, OpenID (0) | 2022.11.08 |
내편 리프레시 토큰 도입기 (1) | 2022.10.13 |