웹 캐시는 자주 쓰이는 문서의 사본을 자동으로 보관해주는 HTTP 장치로 다음과 같은 혜택을 준다.
불필요한 데이터 전송은 값비싼 네트워크 대역폭을 잡아먹고 전송을 느리게 만들며, 웹 서버에 부하를 준다.
캐시를 이용함으로써 원 서버가 중복해서 트래픽을 주고받는 낭비를 줄일 수 있다.
클라이언트가 빠른 LAN에 있는 캐시로부터 사본을 가져온다면, 캐싱은 성능을 대폭 개선하여 네트워크의 병목을 줄여준다.
캐싱은 갑작스런 사건으로 인해 많은 사람이 동시에 같은 웹 문서에 접근할 때 대처하기 위해 특히 중요하다.
모든 네트워크 라우터는 제각각 인터넷 트래픽을 지연시키며, 클라이언트와 서버 사이에 라우터가 적다 하더라도 빛의 속도 그 자체가 유의미한 지연을 유발시킬 수 있다.
그러므로 클라이언트 근처에 캐시를 설치해서 데이터가 전송되는 물리적인 거리를 줄이면 이러한 지연 또한 줄일 수 있다.
캐시가 세상 모든 문서의 사본을 저장하지는 않는다.
캐시에 요청이 도착했을 때, 그에 대응하는 사본이 있다면 그 사본을 이용해 요청이 처리된다. 이것을 캐시 적중(cache hit)이라고 부른다.
반대로 그에 대응하는 사본이 없다면 그냥 원 서버로 전달되기만 하는데, 이것을 캐시 부적중(cache miss)라고 부른다.
원 서버의 콘텐츠는 언제라도 변경될 수 있기 때문에, 캐시는 반드시 그들이 갖고 있는 사본이 최신인지 때때로 점검해야 한다. 이러한 과정을 HTTP 재검사라 부른다.
캐시된 객체 재확인을 위해 가장 많이 쓰이는 도구는 If-Modified-Since
헤더다. 서버에게 보내는 GET 요청에 이 헤더를 추가하면 캐시된 시간 이후에 변경된 경우에만 사본을 보내달라는 의미가 된다.
다음은 GET If-Modified-Since
요청이 서버에 도착했을 때 일어날 수 있는 세가지 상황이다.
만약 원 서버의 객체가 변경되지 않았다면, 서버는 클라이언트에게 HTTP 304 Not Modified 응답을 클라이언트에게 보낸다.
만약 원 서버의 객체가 캐시된 사본과 다르다면, 서버는 콘텐츠 전체와 함께 HTTP 200 OK 응답을 클라이언트에게 보낸다.
만약 원 서버의 객체가 삭제되었다면, 서버는 404 Not Found 응답을 돌려보내며, 캐시는 사본을 삭제한다.
캐시가 요청을 처리하는 비율을 캐시 적중률 또는 문서 적중률이라고 부른다.
문서들이 모두 같은 크기인 것은 아니기 때문에, 문서 적중률이 모든 것을 말해주지 않는다.
바이트 단위 적중률은 캐시를 통해 제공된 모든 바이트의 비율을 표현한다. 이 측정값은 트래픽이 절감된 정도를 포착해낸다.
문서 적중률을 개선하면 전체 대기 시간이 줄어들고, 바이트 단위 적중률의 개선은 대역폭 절약을 최적화 한다.
클라이언트가 응답이 캐시에서 왔는지 알아내는 방법은 Date 헤더를 이용하는 것이다. 응답의 Date 헤더 값을 현재 시각과 비교하여 응답의 생성일이 더 오래되었다면 응답이 캐시된 것이라는 것이다.
한 명에게만 할당된 캐시를 개인 전용 캐시(private cache)라 부르고, 수많은 사용자들 간에 공유된 캐시는 공용 캐시(public cache)라고 부른다.
대부분의 브라우저는 자주 쓰이는 문서를 개인용 컴퓨터의 디스크와 메모리에 캐시해 놓고, 사용자가 캐시 사이즈와 설정을 수정할 수 있도록 허용한다.
프락시 캐시는 로컬 캐시에서 문서를 제공하거나, 사용자의 입장에서 서버에 접근한다. 여러 사용자가 접근하기 때문에, 불필요한 트래픽을 줄일 수 있는 더 많은 기회가 있다.
작은 캐시에서 캐시 부적중이 발생했을 때 더 큰 부모 캐시가 남은 트래픽을 처리하도록 만드는 계층을 만드는 방식이 합리적인 경우가 많다.
이 아이디어는 클라이언트 주위에는 작고 저렴한 캐시를 사용하고, 계층 상단에는 많은 사용자들에 의해 공유되는 문서를 유지하기 위해 더크고 강력한 캐시를 사용하자는 것이다.
몇몇 네트워크 아키텍처는 캐시 계층 대신 복잡한 캐시망을 사용하여, 어떤 부모 캐시와 통신할 것인지, 아니면 요청이 캐시를 완전히 우회해서 원 서버로 바로 가도록 할것인지에 대한 결정을 동적으로 내릴 수 있다.
또한 한층 더 복잡한 캐시 사이의 관계는 서로 다른 조직들이 상호 이득을 위해 그들의 캐시를 연결하여 서로를 찾아볼 수 있도록 할 수 있는데, 이렇게 선택적인 피어링을 지원하는 캐시를 형제 캐시라고 부른다.
단, HTTP는 형제 캐시를 지원하지 않기 때문에 ICP나 HTCP 같은 프로토콜을 이용해 HTTP를 확장한다.
HTTP는 Cache-Control과 Expires라는 헤더를 이용해서 원 서버가 각 문서에 유효기간을 붙일 수 있게 해준다.
Cache-Control: max-age
: 문서의 최대 나이Expires
: 문서의 절대 유효기간. 만약 유효기간이 경과했다면 그 문서는 더이상 신선하지 않음을 나타낸다.캐시된 문서가 만료되었다는 것은, 현재 그 문서가 원 서버의 문서와 다르다는 것을 의미하는 것이 아니라, 그것을 이제 검사할 시간이 되었다는 것을 뜻한다. 이러한 검사 과정을 '서버 재검사'라고 부른다.
서버 재검사 결과 만약 원 서버의 콘텐츠가 변경되었다면, 캐시는 새로운 사본을 가져와 저장하고 클라이언트에게도 보내준다. 콘텐츠가 변경되지 않았다면, 새 만료일일 포함한 새 헤더들만 가져와서 캐시 안의 헤더들을 갱신한다.
HTTP는 캐시가 서버에게 '조건부 GET'이라는 요청을 보낼 수 있도록 해준다. 이 요청은 서버가 갖고 있는 문서가 캐시가 갖고 있는 것과 다른 경우에만 객체 본문을 보내달라고 하는 것이다.
캐시 재검사를 할 때 가장 유용한 조건부 요청 헤더는 If-Modified-Since와 If-None-Match다.
If-Modified-Since: <date>
: 만약 문서가 주어진 날짜 이후로 수정되었다면 요청 메서드를 처리한다.If-None-Match: <tags>
: 마지막 변경된 날짜를 맞춰보는 대신 서버는 특별한 태그를 제공할 수 있다. 이 헤더는 캐시된 태그가 서버에 있는 문서의 태그와 다를 때만 요청을 처리한다.HTTP는 문서가 만료되기 전까지 얼마나 오랫동안 캐시될 수 있게 할 것인지 서버가 설정할 수 있는 여러 가지 방법을 정의한다.
no-store와 no-cache 헤더는 캐시가 검증되지 않은 캐시된 객체로 응답하는 것을 막는다.
이 헤더는 신선하다고 간주되었던 문서가 서버로부터 온 이후로 흐른 시간이며, 초로 나타낸다.
서버는 최대 나이 먹음(maximum aging)을 0으로 설정함으로써, 캐시가 매 접근마다 문서를 캐시하거나 리프레시하지 않도록 요청할 수 있다.
더 이상 사용하지 않기를 권하는 Expires 헤더는 초 단위의 시간 대신 실제 만료 날짜를 명시한다.
이 헤더는 캐시가 이 객체의 신선하지 않은 사본을 원 서버와의 최초의 재검사 없이는 제공해서는 안 됨을 의미한다.
만약 응답이 Cache-Control: max-age 헤더나 Expires 헤더 중 어느 것도 포함하지 않는다면, 캐시는 경험적인 방법으로(heuristic) 최대 나이를 계산할 것이다.
웹 브라우저는 브라우저나 프락시 캐시의 신선하지 않은 콘텐츠를 강제로 갱신시켜 주는 리프레시나 리로드 버튼을 갖고 있다. 이 버튼은 Cache-control 요청 헤더가 추가된 GET 요청을 발생시켜서 강제로 재검사하거나 서버로부터 콘텐츠를 무조건 가져온다.
아파치 웹 서버가 캐시 제어를 어떻게 지원하는지 알아보자.
이 모듈은 개별 헤더들을 설정할 수 있게 해준다.
이 모듈은 적절한 만료 날짜가 담긴 Expires 헤더를 자동으로 생성하는 프로그램 로직을 제공한다.
이 모듈은 HTTP 헤더들의 파일을 특정 객체와 연결시켜준다.
📌 데이빗 고울리, 브라이언 토티, 마조리 세이어, 세일루 레디, 안슈 아가왈 공저 이응준, 정상일 공역, 『HTTP 완벽 가이드: 웹은 어떻게 동작하는가』, 인사이트(2014)