http 캐시는 특정 응답의 요청을 어딘가에 저장해놨다가. 동일한 요청이 올 때 재사용 하는 것을 말한다.
응답을 재사용하면 서버까지 질의를 하는 과정을 거치지 않으니 클라이언트 측에서는 빠른 응답을, 서버 측에서는 부하의 감소를 기대할 수 있다.
url | response |
---|---|
https://hamad.com/index.html | … |
https://hamad.com/style.css | body {…} |
https://hamad.com/script.js | function main () {…} |
보통 http 캐시는 위와 같이 url을 키 값으로, 응답을 value로 하여 저장한다.
http 캐시는 크게 Private 캐시와 Shared 캐시로 구분할 수 있다.
쉽게 말하면 웹 브라우저 단에서 존재하는 캐시이다. 이것이 왜 private일까? 브라우저는 내 컴퓨터에만 있기 때문에 다른 사용자들이 접근할 수 없기 때문이다. 따라서 이곳에는 사용자의 개인화된 응답을 저장할 수 있는 것이다.
주의할 점은, 만약 서버의 응답에 “Authorization”헤더가 존재한다면 브라우저는 이 정보를 private 캐시에 저장하지 않는다.
Shared Cache는 다시 Proxy와 Managed로 구분할 수 있다
클라이언트와 서버 사이에서 http 메시지를 전달하는 역할이다. 이 프록시 캐시는 개발자들이 직접 제어할 수 없고 http 오직 헤더를 통해서 캐시 정보를 전달하기만 할 뿐이다. 포워드 프록시에서 동작하는 캐시이다.
개발자들이 제어할 수 있는 캐시이다. 리버스 프록시(nginx 등), 혹은 cdn(Aws CloudFront 등)을 의미한다. nginx에서 캐시 관련 설정을 직접 할 수 있으며, cdn도 제공 업체에 대시보드를 통해 설정할 수 있다.
서버에서 언제까지 해당 캐시의 응답을 유효하다고 판단할지, 그 유효기간을 지정할 수 있다. Http 헤더의 Date에 응답 생성 시간을 적고, Cache-Control에 최대 유효시간 초를 설정한다.
HTTP/1.1 200 OK
Content-Type : ...
Content-Length : ...
Date : Tue, 30 Jul 2023 11:11:11 GMT
Cache-Control : max-age:604800
위와 같이 Cache-Control의 max-age:604800로 설정하여, 604800초, 즉 일 주일 동안 유효한 캐시를 설정해두었다.
유효기간이 지나기 전의 캐시 상태를 Fresh(신선함)라고 표현하며, 유효기간이 지난 캐시 상태를 Stale(오래됨, 만료됨)이라고 표현한다.
그런데, Stale한 캐시라고 해서 무조건 쓰레기통에 던져버리면 될까? 유효 기간이 지났어도 서버에서 갱신되지 않아 여전히 유효한 캐시들이 있을 텐데, 단순히 시간이 지났다고 날려버리고 다시 서버를 타면 그만큼의 불필요한 리소스가 낭비될 것이다. 따라서 유효시간이 지난 캐시가 아직 유효한지를 판단하는 검증이 필요하다. 이를 유효성 검증(validation), 혹은 재검증(revalidation)이라 한다.
재검증을 하는 방식은 “조건부 요청”을 통해서 진행한다. 날짜 기준으로만 확인하는 If-Modified-Since와 태그 값을 통해 확인하는 ETag/If-None-Match 방법이 존재한다.
GET /index.html HTTP/ 1.1
Host : hamad.com
Accept : text/html
If-Modified-Since : Tue, 30 Jul 2023 21:00:00 GMT
만약 위와 같은 재검증 요청에 다음과 같은 200 OK 응답을 받았다면 캐시된 요청이 서버에서 갱신됐다는 뜻이다.
HTTP/1.1 200 OK
Content-Type : text/html
Content-Length : ...
Date : Tue, 30 Jul 2023 22:22:22 GMT
Last-Modified : Tue, 30 Jul 2023 22:00:00 GMT
Cache-Control : max-age:3600
<!doctype html>
...
Date는 응답이 생성된 시간이며, Last-Modified는 서버에서 해당 자원이 수정된 시간이다. 자원이 수정되었으므로 body에 html 응답 값이 새로 들어온다
브라우저는 응답받은 last-modified 헤더를 기준으로 유효 시간(3600초=1시간)이 지나면 다시 아래와 같은 get 요청을 통해 재검증을 한다. 아래의 GET 요청에서 브라우저가 보내는 If-Modified-Since 헤더 값은 이전 응답에서 받은 Last-Modified 헤더의 값이다.
GET /index.html HTTP/ 1.1
Host : hamad.com
Accept : text/html
If-Modified-Since : Tue, 30 Jul 2023 22:00:00 GMT
만약 다시 유효시간이 지나 위와 같은 재검증 요청을 했는데도, 캐시가 갱신되지 않았다면 304 Not Modified 응답을 받는다. 이 때는 캐시가 유효하므로 body에 아무 응답이 없다.
HTTP/1.1 304 Not Modified
Content-Type : text/html
Content-Length : ...
Date : Tue, 30 Jul 2023 23:22:22 GMT
Last-Modified : Tue, 30 Jul 2023 22:00:00 GMT
Cache-Control : max-age:3600
Last-Modified 값이 요청의 If-Modified-Since값과 동일하므로 갱신되지 않음을 확인할 수 있다. 304 응답은 body에 아무런 값이 없다.
그러나 If-Modified-Since는 초 단위까지는 비교가 가능하나, 밀리세컨드 단위까지는 비교가 불가능하다. 따라서 이후에 설명할 방법론에 비해 상대적으로 정교한 캐시 체크를 수행할 수 없다는 단점이 있다.
따라서 해시 값의 태그번호를 헤더에 넣어 보내는 ETag/If-None-Match(쉽게 ETag) 방식이 존재한다.
브라우저가 요청 시 서버는 해당 자원 요청에 임의의 해시 값을 부여하고, 응답의 헤더 값의 “Etag” 필드에 넣어준다.
HTTP/1.1 200 OK
Content-Type : text/html
Content-Length : ...
Date : Tue, 30 Jul 2023 22:22:22 GMT
Etag : "ssdafjka" // 임의의 해시 태그값 부여
Cache-Control : max-age:3600
<!doctype html>
...
캐시 유효기간인 3600초 이후 재요청을 보내면 “If-None-Match” 헤더에 이전에 응답받은 해시값을 넣어 보낸다.
GET /index.html HTTP/ 1.1
Host : hamad.com
Accept : text/html
If-None-Match : "ssdafjka"
서버는 해시 값을 보고 해시값에 해당하는 자원을 확인하여, 이전과 같이 갱신되면 200, 갱신되지 않았으면 304를 응답한다.
태그 값을 활용하니 이제 시간과는 전혀 상관없이 갱신 여부를 체크할 수 있다.
강제 재검증은 항상 최신화를 하는 것이다. 아래와 같이 응답의 캐시 컨트롤 헤더에 no-cache가 부여되면, 일단 캐시에는 저장을 하나, 요청이 올 때마다 항상 서버에게 갱신 여부를 질의하고 200, 304 응답 여부에 따라 캐시의 사용 여부를 검증한다.
HTTP/1.1 200 OK
Content-Type : text/html
Content-Length : ...
Date : Tue, 30 Jul 2023 22:22:22 GMT
Last-Modified : Tue, 30 Jul 2023 22:00:00 GMT
Etag : "ssdafjka"
Cache-Control : no-cache
<!doctype html>
...
참고로 no-cache 이외에도 no-store 방식이 존재하는데, 일단 캐시를 저장하는 no-cache와 달리 no-store는 캐시 자체를 저장하지 않는다 한다.
Cache-Control
헤더가 존재하지 않으면, 기본적으로 브라우저나 서버가 지정한 기본 동작에 의해 캐시를 한다.(크롬의 경우 약 1년 정도 캐싱을 한다고 함)