같은 이미지를 볼 때마다 매번 서버에서 새로 내려받는다면 웹은 지금처럼 빠르게 동작하지 못할 것이다. 한 번 받은 자원을 저장해 두었다가 다시 쓰는 캐시가 있기에, 반복되는 요청의 상당수가 서버까지 가지 않고도 해결된다. 이 저장과 재사용의 규칙을 정하는 것이 캐시 제어 헤더다. 캐시를 제대로 다루면 성능과 비용이 함께 개선된다.


캐시는 잘 쓰면 강력하지만 잘못 쓰면 골칫거리가 된다. 바뀐 자원이 갱신되지 않고 옛것이 계속 보이거나, 반대로 캐시해도 될 자원을 매번 새로 받아 서버를 혹사하는 일이 벌어진다. 이번 편은 캐시가 어떤 원리로 동작하는지, 신선도와 검증이라는 두 축이 무엇인지, 그리고 캐시 제어 헤더의 지시자들을 어떻게 조합하는지를 다룬다.

캐시가 필요한 이유

캐시의 목적은 반복되는 전송을 줄이는 것이다. 자주 바뀌지 않는 자원을 매 요청마다 서버에서 받아 오는 것은 낭비다. 한 번 받아 저장해 두고 그것을 재사용하면, 응답이 빨라지고 서버의 부하가 줄며 네트워크 사용량도 감소한다. 이 세 가지 이득이 캐시를 쓰는 이유다.


캐시는 여러 곳에 존재한다. 사용자의 브라우저 안에도 있고, 사용자와 서버 사이의 중간 서버에도 있으며, 서버 앞단의 배포망에도 있다. 각 위치의 캐시가 자기 층에서 응답을 저장했다가 재사용하므로, 요청이 서버까지 도달하기 전에 여러 단계에서 캐시로 해결될 수 있다. 캐시가 겹겹이 쌓여 서버의 부담을 덜어 주는 구조다.


캐시의 이득이 큰 만큼, 무엇을 캐시하고 무엇을 캐시하지 않을지의 판단이 중요하다. 자주 바뀌는 데이터를 오래 캐시하면 사용자가 낡은 정보를 보게 되고, 개인마다 다른 데이터를 공용 캐시에 저장하면 다른 사람의 정보가 노출될 수 있다. 캐시는 성능의 도구이면서 동시에 정확성과 보안의 위험 요소이기도 하다.


그래서 캐시 제어의 핵심은 각 자원에 맞는 규칙을 정확히 부여하는 데 있다. 오래 유지해도 되는 자원은 길게 캐시하고, 자주 바뀌는 자원은 짧게 캐시하거나 매번 확인하게 하며, 개인 정보가 담긴 자원은 공용 캐시에 저장되지 않게 막는다. 이 세밀한 조정을 가능하게 하는 것이 캐시 제어 헤더다.


캐시를 이해할 때 붙들어야 할 두 개의 축이 있다. 하나는 저장된 사본을 서버에 묻지 않고 얼마 동안 쓸 수 있는지를 정하는 신선도이고, 다른 하나는 신선도가 끝난 사본이 아직 유효한지를 서버에 확인하는 검증이다. 캐시의 거의 모든 동작이 이 두 축의 조합으로 설명된다. 신선도가 살아 있으면 서버에 아예 가지 않고, 신선도가 끝나면 검증으로 최소한의 확인만 한다. 이 두 축을 분리해서 기억하면 복잡해 보이는 캐시 헤더들도 각각 어느 축에 속하는지로 정리된다.

신선도와 만료

캐시의 첫 번째 축은 신선도다. 저장된 사본이 얼마 동안 유효한지를 정하는 개념이다. 서버가 이 자원은 일정 시간 동안 신선하다고 알려 주면, 캐시는 그 시간이 지나기 전까지 서버에 묻지 않고 저장된 사본을 그대로 내어 준다. 이 기간 안의 요청은 서버까지 가지 않으므로 가장 빠르다.


신선도의 기간을 정하는 대표적인 방식이 최대 수명을 지정하는 것이다. 이 자원을 받은 뒤 몇 초 동안 신선하다고 명시하면, 캐시는 그 시간 동안 사본을 재사용한다. 시간이 지나 사본이 낡으면, 캐시는 그것을 곧바로 버리지 않고 서버에 아직 유효한지를 확인하는 절차로 넘어간다. 신선도가 끝났다고 사본이 무조건 폐기되는 것은 아니다.


신선도 기간을 얼마로 정할지는 자원의 성격에 달렸다. 한 번 만들어지면 내용이 바뀌지 않는 자원은 아주 길게 잡아도 된다. 파일 이름에 고유한 표시를 붙여 내용이 바뀌면 이름도 바뀌게 하면, 그 파일은 사실상 영구히 캐시해도 안전하다. 반대로 수시로 바뀌는 자원은 신선도를 짧게 잡거나 아예 두지 않는다.


신선도를 잘못 잡으면 두 방향의 문제가 생긴다. 너무 길게 잡으면 바뀐 자원이 갱신되지 않아 사용자가 낡은 것을 보고, 너무 짧게 잡으면 캐시의 이득이 줄어 서버가 불필요하게 바빠진다. 자원마다 얼마나 자주 바뀌는지를 헤아려 적절한 기간을 부여하는 것이 캐시 설계의 기본이다.

Cache-Control의 지시자들

캐시 동작을 세밀하게 지시하는 헤더가 Cache-Control 이다. 이 헤더는 여러 지시자를 쉼표로 나열해 캐시의 행동을 조정한다. 최대 수명을 정하는 지시자, 공용 캐시에 저장해도 되는지를 정하는 지시자, 매번 확인을 요구하는 지시자 등이 조합되어 하나의 정책을 이룬다.


공용 캐시와 개인 캐시를 구분하는 지시자가 특히 중요하다. 어떤 자원은 여러 사용자가 공유하는 중간 캐시에 저장해도 되지만, 사용자마다 다른 개인 데이터는 그래서는 안 된다. 개인 전용임을 명시하는 지시자를 붙이면, 그 자원은 사용자의 브라우저에만 저장되고 공용 캐시에는 저장되지 않는다. 이 구분을 소홀히 하면 한 사람의 데이터가 다른 사람에게 보이는 심각한 사고가 난다.


캐시를 아예 금지하거나 매번 검증을 요구하는 지시자도 있다. 저장 자체를 막는 지시자를 붙이면 그 응답은 어디에도 저장되지 않는다. 저장은 하되 재사용 전에 반드시 서버에 유효한지 물으라는 지시자도 있다. 민감한 정보나 항상 최신이어야 하는 데이터에는 이런 강한 지시자를 쓴다.


지시자들은 서로 조합되어 정교한 정책을 만든다. 예를 들어 개인 전용으로 지정하면서 짧은 수명을 함께 주면, 사용자의 브라우저에만 잠시 저장되는 자원이 된다. 공용 저장을 허용하면서 긴 수명을 주면, 배포망에 오래 남는 자원이 된다. 각 지시자의 의미를 알고 목적에 맞게 조합하는 것이 캐시 제어의 실력이다.

검증과 조건부 요청

캐시의 두 번째 축은 검증이다. 저장된 사본이 신선도를 다했을 때, 그것을 버리고 새로 받는 대신 아직 유효한지를 서버에 확인하는 절차다. 이 확인을 위해 캐시는 조건부 요청을 보낸다. 내가 가진 사본이 여전히 최신이냐고 묻는 요청이다.


서버는 이 조건부 요청에 두 가지로 답할 수 있다. 사본이 여전히 유효하면 본문 없이 아직 그대로라는 짧은 응답을 돌려주고, 캐시는 저장해 둔 사본을 계속 쓴다. 사본이 낡았으면 새 내용을 본문에 담아 돌려준다. 유효한 경우 본문이 오지 않으므로, 전송량이 크게 줄어드는 것이 이 절차의 이득이다.


이 검증 방식은 신선도와 함께 쓰일 때 위력을 발휘한다. 신선도가 살아 있는 동안은 서버에 아예 묻지 않아 가장 빠르고, 신선도가 끝난 뒤에는 조건부 요청으로 확인만 하므로 본문을 다시 받는 낭비를 피한다. 자원이 실제로 바뀌지 않았다면, 캐시는 최소한의 통신만으로 사본을 계속 재사용할 수 있다.


조건부 요청이 성립하려면 서버가 사본에 확인용 표시를 붙여 주어야 한다. 이 표시가 캐시에 저장되어 있다가, 조건부 요청을 보낼 때 서버로 되돌아간다. 서버는 그 표시를 보고 현재 자원과 비교해 사본이 유효한지 판단한다. 이 표시를 무엇으로 삼느냐에 따라 검증의 정밀도가 달라진다.

강한 검증자와 약한 검증자

사본이 유효한지 판단하는 표시를 검증자라고 부른다. 검증자에는 두 종류가 있다. 하나는 자원이 마지막으로 바뀐 시각을 쓰는 방식이고, 다른 하나는 자원의 내용을 요약한 고유한 값을 쓰는 방식이다. 두 방식은 정밀도와 비용에서 차이가 있다.


바뀐 시각을 검증자로 쓰는 방식은 단순하지만 한계가 있다. 시각은 초 단위로만 다루어지는 경우가 많아, 같은 초 안에 두 번 바뀐 변화를 구분하지 못한다. 또 내용은 그대로인데 시각만 갱신되면, 실제로는 바뀌지 않았는데도 바뀐 것으로 오판할 수 있다. 대략적인 검증에는 충분하지만 정밀함이 필요한 곳에는 부족하다.


내용을 요약한 고유 값을 검증자로 쓰는 방식은 더 정밀하다. 내용이 조금이라도 바뀌면 이 값도 바뀌므로, 자원의 실제 변화를 정확히 반영한다. 시각 방식이 놓치는 미세한 변화까지 잡아낸다. 대신 이 값을 계산하는 데 약간의 비용이 들고, 서버가 그 값을 관리해야 하는 부담이 따른다.


두 검증자는 상황에 맞게 선택하거나 함께 쓴다. 정확성이 크게 중요하지 않은 정적 자원에는 시각 방식으로 충분하고, 미세한 변화도 놓치면 안 되는 자원에는 고유 값 방식이 어울린다. 어느 쪽이든 검증자가 있어야 조건부 요청이 성립하므로, 캐시를 제대로 쓰려면 서버가 검증자를 붙여 주어야 한다.

캐시의 위치와 공개 여부

캐시가 어디에 놓이느냐에 따라 다루는 방식이 달라진다. 사용자의 브라우저 안에 있는 개인 캐시는 그 사용자만 쓰므로 개인 데이터를 저장해도 안전하다. 반면 여러 사용자가 공유하는 중간 캐시나 배포망의 공용 캐시에는 개인 데이터를 저장하면 안 된다. 위치의 성격이 저장 가능한 내용을 가른다.


그래서 자원마다 어느 캐시까지 허용할지를 명확히 정해야 한다. 누구에게나 같은 공개 자원은 공용 캐시에 저장해 널리 재사용하게 하고, 사용자별로 다른 자원은 개인 캐시에만 저장되게 제한한다. 이 구분을 헤더로 정확히 표현하지 않으면, 공용 캐시가 개인 데이터를 붙들어 다른 사람에게 내어 주는 사고가 일어난다.


배포망의 캐시는 특히 강력하면서 위험하다. 전 세계에 흩어진 캐시가 응답을 저장해 사용자와 가까운 곳에서 내어 주므로 성능 이득이 크지만, 한 번 잘못 저장되면 그 잘못이 널리 퍼진다. 그래서 배포망 앞에서는 무엇을 캐시할지를 더욱 신중히 정하고, 잘못 저장된 것을 지우는 방법도 함께 마련해 두어야 한다.


공개 여부의 판단이 어려울 때는 안전한 쪽을 택하는 것이 원칙이다. 이 자원이 개인 정보를 담을 수 있는지 확신이 서지 않으면, 공용 캐시 저장을 막아 두는 편이 낫다. 성능을 위해 캐시를 넓게 열었다가 정보가 새는 것보다, 다소 보수적으로 닫아 두는 편이 훨씬 안전하다. 캐시의 편의와 보안이 충돌할 때는 보안을 앞세워야 한다.

캐시 전략의 실제

실무에서 캐시 전략은 자원의 종류를 나누는 데서 시작한다. 내용이 바뀌면 이름도 바뀌는 정적 자원은 아주 길게 캐시하고, 자주 갱신되는 데이터는 짧게 캐시하거나 매번 검증하게 하며, 개인 데이터는 개인 캐시에만 두거나 아예 캐시하지 않는다. 이 세 부류로 나누는 것만으로 대부분의 자원을 다룰 수 있다.


정적 자원에 이름 바꾸기 기법을 쓰는 것이 특히 유용하다. 파일의 내용을 요약한 표시를 이름에 붙여, 내용이 바뀌면 이름이 달라지게 하면, 그 파일은 영구히 캐시해도 안전하다. 바뀐 자원은 새 이름을 가지므로 자동으로 새로 받아지고, 바뀌지 않은 자원은 옛 이름 그대로 캐시에서 재사용된다. 신선도와 갱신을 동시에 해결하는 방법이다.


캐시 전략을 세울 때는 잘못 저장된 것을 어떻게 지울지도 미리 정해야 한다. 아무리 신중히 설정해도 실수는 생기고, 급히 바꿔야 할 자원이 캐시에 붙들리는 상황이 온다. 이럴 때 배포망의 캐시를 비우는 방법이나, 이름을 바꿔 새 자원으로 우회하는 방법을 준비해 두면 사고에 빠르게 대응할 수 있다.


캐시는 성능을 위한 도구이지만, 그 관리는 결국 정확성과 보안을 지키는 규율에 가깝다. 빠르게 만들려다 낡은 정보를 보여 주거나 남의 정보를 노출하면, 얻은 속도보다 잃은 신뢰가 크다. 그래서 캐시를 다룰 때는 항상 이 자원이 언제까지 유효한지와 누구에게 보여도 되는지를 먼저 묻는 습관이 필요하다. 이 두 질문에 분명히 답할 수 있으면 캐시 설정은 자연스럽게 따라온다.


이번 편은 캐시가 필요한 이유와 신선도와 검증이라는 두 축, 캐시 제어 헤더의 지시자, 검증자의 종류, 그리고 캐시의 위치에 따른 전략을 다루었다. 캐시가 같은 자원을 아껴 재사용하는 규칙이라면, 서버가 여러 형태의 자원 중 어느 것을 내어 줄지는 또 다른 협상의 문제다. 다음 편에서는 클라이언트와 서버가 형식과 언어를 맞추는 콘텐츠 협상을 살펴본다.