캐시(Cache) ?
개요
클라이언트는 서버에게 요청을 하여 원하는 자원을 얻어낸다. 사람들은 저마다 자주 사용하는 웹 사이트가 있고, 그것은 같은 요청을 자주 서버에게 보내는 것과 같다. 내가 원하는 자원은 매번 같은데 꼭 서버로 부터 매번 받아야 할까?
웹 브라우저에서는 캐시 저장소가 존재하여 요청에 대한 응답을 저장해 놓고 사용할 수 있다. 이번 포스팅에서는 그 기술인 Cache의 개념을 배우고 Cache가 관리되는 과정을 적어보려한다.
목차
- Cache
- Last-Modified
- ETag
- 프록시 캐시 서버
- Cache-Control
- 완전한 캐시 무효화
Cache
여기서 Cache란, 서버로부터 받은 응답을 저장하는 것을 말한다. 서버와 클라이언트 사이의 통신은 네트워크를 타기 때문에 메모리, 디스크에서 자원을 가져오는 것보다 훨씬 비용이 크고 느리다. Cache는 한번 요청했던 자원을 다음에 요청할 때, 네트워크의 비용을 줄여주는 기술이다.
응답 메시지의 헤더에 캐시 지시어인 cache-control로 수명(max-age)을 표기하여 클라이언트에게 전달된다. 클라이언트는 지시에 따라 요청에 대한 응답을 브라우저 캐시에 저장해 놓고, 두 번째 요청부터는 브라우저 캐시에서 조회하여 사용할 수 있다.
클라이언트와 서버의 통신 중에 캐시가 사용되는 과정은 다음과 같다. 응답메시지의 용량은 헤더 0.1M 와 바디 1M로 총 1.1M으로 가정한다.
- 클라이언트가 서버에게 요청을 하기 전, 브라우저 캐시에서 우선하여 찾고 없다면 서버에게 요청한다.
- 서버는 클라이언트에게 캐시 지시어를 포함하여 응답한다. (200 OK, 1.1Mbyte)
- 클라이언트는 브라우저 캐시에 저장한다.
- 클라이언트가 서버에게 같은 요청을 해야한다면, 브라우저 캐시에 저장된 캐시를 조회한다.
- 캐시가 유효한지 서버에게 요청한다.
- 서버는 캐시가 유효한지 확인한 후
- 이상이 없다면 캐시를 사용하라는 응답을 한다. 응답 메시지의 바디는 없다.(304 Not Modified, 0.1M)
- 이상이 있다면 2번의 응답을 다시 보내준다.
Last-Modified
캐시된 자원이 항상 서버에서 관리하는 자원과 항상 같을까? 그것은 알 수 없다. 그래서 캐시된 자원이 유효한지 확인이 필요하다. 서버는 last-modified를 통해 캐시가 최종적으로 수정된 날짜를 초단위 까지 표기하여 클라이언트에게 전달할 수 있다.
클라이언트는 브라우저 캐시에 요청에 대한 응답이 존재하고 last-modified가 있다면 다음과 같이 요청하여 검증을 요청할 수 있다.
if-modified-since는 내가 갖고있는 캐시가 변하지 않았는지 서버에게 보내는 조건부 요청의 표기이다. 조건부 요청이라 부르는 이유는 해당 요청의 검증 결과에 따라 응답의 결과가 달라지기 때문이다. 서버에게 "내가 갖고 있는 캐시가 변했니?" 라고 서버에게 요청하며 변했다면 200 OK응답이, 변하지 않았다면 304 Not Modified 응답이 보내진다. (if-unmodified-since도 있으며 이때는 응답이 달라진다.)
ETag
Last-Modified 는 초단위 까지의 시간을 기록하여 검증하는 방법으로 다음과 같은 단점이 있다.
- 날짜를 기반으로 검증이 이루어져서 별도로 캐시 로직을 사용할 수 없다.
- 데이터를 몇번 수정해서 날짜는 다르지만 결과적으로 데이터는 같은 경우 효과가 없다.
- 1초 미만 단위로 조정이 불가능하다.
ETag는 캐시에 고유한 버전 이름을 달아둠으로써 Last-Modified의 단점을 보완할 수 있다.
etag는 위의 이미지와 같이 해싱을 통해 버전이름을 표기할 수 있으며 데이터가 변경되면 이 이름을 바꾸어서 응답한다. 클라이언트는 다음과 같이 조건부 요청을 할 수 있다.
if-none-match는 "내가 갖고 있는 캐시의 버전이 너와 일치하지 않니?" 라고 서버에게 요청하며 결과에 따라 변했다면 200 OK, 변하지 않았다면 304 Not Modified를 응답한다. (if-match 도 있으며 이땐 응답의 결과가 다르다.)
ETag는 캐시 제어 로직을 서버에서 완전히 관리한다는 장점이 있다.
프록시 캐시 서버
Google의 서버와 우리나라의 물리적 거리는 굉장히 멀다. 그만큼 통신의 비용과 속도가 느릴 수 밖에 없는데 이것을 극복하기 위해 근거리에 캐시 역할을 하는 서버를 둔다. 이때 미국에 있는 Google의 서버를 origin server, 한국에 둔 캐시 서버를 프록시 (캐시) 서버라 한다.
프록시 서버의 역할은 사용자들의 요청을 원서버 대신 받아서 처리한다. 이때 갖고있지 않은 자원은 origin server로부터 다운로드하여 응답해준다. 다운로드한 자원은 계속 갖고 있기 때문에 두번째 사람부터는 빠르게 응답을 받을 수 있다.
프록시 서버에서 저장하는 캐시는 여러 사람으로부터 사용되기 때문에 public cache라 하고, 웹 브라우저에서 사용되는 캐시는 개인이 사용하기 때문에 private cache라 한다.
Cache-Control
서버는 클라이언트에게 cache-control을 통해 캐시를 어떤식으로 다뤄야 하는지 지시한다.
- cache-control: XXX
- max-age: 캐시의 유효 시간을 알려준다. (초단위)
- no-cache: 데이터는 캐시해도 되지만, 항상 origin server로부터 검증을 받고 사용하도록 한다.
- no-store: 캐시를 저장하지 않도록 한다.
- must-revalidate: 캐시 만료 후 최초 조회시 origin server에 검증해야 한다. origin 서버 접근 실패시 반드시 오류(504 Gateway Timeout) 이 발생해야 한다.
- public: 응답이 public cache, private cache에 저장되어도 된다.
- private: 응답이 private cache에 저장되는 것만 허용된다.
- s-maxage: public cache에만 적용되는 max-age
완전한 캐시 무효화
보안에 민감한 데이터는 캐싱하면 안된다. 웹 브라우저는 서버에서 지시하지 않아도 임의로 캐시하는 경우가 있는데, 이를 방지하기 위해 다음과 같은 조치를 취해야 한다.
- cache-control: no-cache, no-store, must-revalidate
- pragma: no-cache
이렇게 많은 옵션을 주는 이유는 HTTP 호환성과 같은 다양한 상황에 대해 예방하기 위해서다.
no-cache와 must-revalidate는 비슷해 보이지만, origin server에 접근 실패시 에러를 반드시 발생시킨다는 점이 다르다. no-cache의 경우 origin server와의 통신이 실패하면 200 OK를 응답하여 성공한 화면(예전 데이터)를 보여주는 경우도 있다고 한다.
pragma는 HTTP 1.0에서 사용되던 옵션이고 호환성을 위해 사용된다.
Reference
- 김영한의 HTTP 웹 기본 지식