캐시는 자주 쓰이는 문서의 사본을 자동으로 보관하는 HTTP
장치이다.
웹 요청이 캐시에 도착했을 때, 캐시된 로컬 사본이 존재한다면 원 서버까지 요청을 보내지 않고 해당 캐시를 제공한다.
이는 다음과 같은 혜택을 제공한다.
하지만 캐시는 세상 모든 문서의 사본을 저장하지 않는다. 그렇기 때문에 캐시 서버에 오는 요청들에 대해 가지고 있는 사본이 있다면, 캐시서버에서 사본을 보내주고 존재하지 않는다면 원서버로 요청을 전달한다.
이처럼 캐시 서버로 온 요청에 대해 사본을 가지고 있는 경우를 캐시 적중(cache hit) , 대응하는 사본이 없어 원 서버로 요청을 전달하는 행위를 캐시 부적중 (cache miss) 라고 한다.
원서버 콘텐츠는 변경될 수 있기 때문에 캐시는 현재 갖고 있는 사본이 여전히 최신인지 서버를 통해 때때로 점검해야 한다.
이러한 신선도 검사를 HTTP 재검사
라고 부른다.
주로 이런 재검사는 클라이언트가 요청을 보낼 때 헤더에 If-Modified-Since
헤더나 If-None-Match
와 같은 헤더를 포함시킬 때 일어난다.
If-Modified-Since
와If-None-Match
If-Modified-Since : <date>
: 캐시 데이터를 보낼 때<date>
이후로 수정되었는지 확인하고 수정되었다면 원서버에서 리소스를 요청해와라
If-None-Match: <etag>
: 클라이언트가 이전에 받은 리소스의 엔터티 태그를 서버에 전달하고, 클라이언트가 받은 엔터티 태그와 서버가 가지고 있는 리소스의 엔터티 태그가 동일한지 확인하고, 동일하지 않다면 원서버에서 리소스를 요청해와라
캐시가 서버에게 자신의 사본이 신선한지 재검사를 요청하면, 신선 할 경우 서버는 캐시에게 304 Not Modified
응답을 보낸다. 그럼 캐시는 해당 응답을 캐시 서버에 저장된 엔터티와 함께 다시 클라이언트에게 건내주면 된다.
재검사는 캐시 적중보단 느리고 캐시 미적중보단 빠르다. 캐시 미적중보다 빠른 이유는 재검사를 했을 때 원 서버에서 데이터를 찾을 필요없이 캐시서버에서 데이터를 꺼내오면 되기 때문이다.
만일 재검사가 부적중이라면 서버는 수정된 데이터를 HTTP 200 OK 라는 응답과 함꼐 클라이언트에게 보낸다.
만일 해당 리소스가 원 서버에서 삭제되었다면 서버는 404 Not Found 응답을 돌려보내고, 캐시는 사본을 삭제한다.
캐시가 요청을 처리하는 비율을 캐시 적중률, 혹은 문서 적중률이라고 한다.
캐시 적중률이 높을 수록 캐시의 사본이 원본 데이터와 같은 것을 의미한다.
오늘날 적중률 40%면 웹 캐시로 괜찮은 편이라고 한다.
문서들이 모두 같은 크기인것이 아니기 때문에 문서의 수로만 표현하는 문서 적중률이 모든 것을 말해주지 않는다.
문서 적중률이 높은 캐시 서버 A , 문서 적중률이 낮은 캐시 서버 B 가 존재 할 때 만약 A는 작은 용량의 데이터들만 적중하는 캐시서버고 , B 는 엄청나게 큰 용량의 데이터들만 적중한다고 해보자
그럼 B는 A에 비해서 적중하는 횟수는 적을지언정 트래픽에 더 크게 기여한다.
그럼으로 적중 횟수에 치중하는 문서적중률
이 아닌 문서의 바이트 단위 적중률을 통해 트래픽 기여도를 계산하기도 한다.
문서 적중률이 높으면 전체 대기시간이 줄어든다. 그 이유는 원서버와
TCP
커넥션을 맺는 횟수를 줄여주기 때문이다.바이트 적중률이 높으면 대역폭을 절약해준다.
불행히도 HTTP 는 클라이언트에게 응답이 캐시 적중이였는지 원 서버 접근인지 말해 줄 수 있는 방법을 제공하지 않는다.
클라이언트가
If-Modified-Since
나If-None-Match
헤더 없이 요청했다면 캐시 서버에서 온건지 원서버에서 온건지 모른다.
그래서 클라이언트가 응답이 캐시에서 왔는지 알아내는 방법은 Data
헤더를 이용하여 응답의 Data
헤더가 요청보다 이전에 일어났다면 캐시에서 온 것임을 알 수 있다.
또 다른 방법은 응답 헤더의 Age
헤더를 이용하는 것이다.
304 상태 코드 예시
네이버에서 소스들을 요청 해올 떄를 살펴보자
GET
요청에 대해 Response
상태 태그가 304
인 것을 보면 우선 요청 헤더에 If-Modified-Since , If-None-Match
헤더를 넣어 요청 한 것을 볼 수 있다.
그럼 캐시는 해당 헤더를 해석해 원 서버에서 수정이 되었는지를 확인하였을 것이고, 수정되지 않았다며 서버에서 응답 받은 304
상태코드와 함께 클라이언트에게 캐시 서버의 데이터를 제공해주었다.
200 상태 코드 예시
나는 2024-01-12 금
에 요청한 헤더에서 응답은 2024-01-11 목
에 만들어졌다고 뜬다.
이는 캐시 서버에 미리 저장되어 있는 응답을 보내준 것이다.
캐시 토폴로지는 캐시 시스템의 구조 및 계층 구조를 나타낸다.
개인 전용 캐시는 한 명에게만 할당된 캐시를 의미한다.
주로 개인 전용 캐시는 개인이 사용하는 웹 브라우저의 캐시에 해당한다.
메모리 캐시
브라우저가 메모리에 일시적으로 저장하는 캐시이다. 개인 사용자의 캐시 데이터를 저장하여 빠른 페이지 로딩을 위해 사용된다. 하지만 브라우저를 닫거나 탭을 닫을 때 데이터가 사라진다.
같은 페이지를 들어가있는 상태에서 새로고침 하는 것과, 브라우저를 닫고 다시 해당 브라우저에 들어가려 하면 속도 차이가 난다. 확인해보자
디스크 캐시
동일하게 브라우저가 저장하지만 브라우저 메모리가 아닌 브라우저 디스크에 저장한다. 브라우저를 종료해도 데이터가 보존되어 다음에 접속 될 때 사용된다.
브라우저 캐시는 주로 웹 페이지의 자주 사용되는 자원들을 저장하여 동일한 자원을 다시 다운로드 하지 않고 빠르게 렌더링 할 수 있게 도와준다.
공용 캐시는 캐시 프록시 서버 또는 프록시 캐시라고 불리는 공유된 프락시 서버이다.
여러 사용자의 요청에 대해 캐시 데이터를 제공한다.
이전 프록시는 부모 자식 관계의 구조를 맺는다고 하였다. 그러면 캐시 서버들도 부모 자식 관계의 구조를 맺는다.
클라이언트로부터 가까운 캐시에서 해당 사본이 존재하지 않는다면 서버 방향으로 존재하는 캐시 서버들로부터 사본을 요청한다.
만약 모든 캐시 서버들이 미적중이라면 원 서버까지 요청이 전달 될 것이다.
프록시 연쇄가 길어질 수록 중간 프록시는 성능 저하가 발생한다.
프록시 연쇄가 길 수록 프록시 마다 요청을 처리하고 응답을 전달하는 동안의 레이턴시가 생긴다.
또한 프록시가 촘촘히 존재 할 경우 미적중 할 확률이 높아진다.
또한 프록세 치엔을 통과하는 단계에서 데이터가 전송되어 대역 폭을 사용하게 됨으로 성능에 부담이 된다 .그래서 프록시를 너무 많이 거치지 않도록 제한한다고 한다.
몇몇 네트워크는 복잡한 캐시망을 만든다 .
요청이 들어왔을 때 캐시서버를 건널지, 완전히 우회해서 원 서버로 바로 가도록 할지 등을 캐시 커뮤니케이션을 통해 동적으로 내린다.
더 복잡한 관계는 부모-자식 관계의 캐시 서버 뿐이 아니라 서로 다른 조직 간의 캐시를 연결하여 형제 관계까지 구성한다. 이는 나중에 더 이야기 하도록 한다.
캐시 처리 단계는 원 서버와의 통신과 크게 차이가 없다. 다만 요청한 정보가 캐시 서버에 존재하는가 존재하지 않는가를 확인한다는 차이가 있다.
캐시는 네트워크 커넥션에서의 활동을 감지하고, 들어오는 데이터를 읽어드린다. 이 때 클라이언트가 www.naver.com (예시)
이란 주소에 정보를 요청해도 네이버에서 운영하는 프록시 서버와 TCP 커넥션
이 이뤄진다.
고성능 프록시 서버는 메시지 전체를 읽기도 전에 읽을 수 있는 정도의 정보가 도착하면 트랜잭션 처리를 시작한다고 한다.
캐시는 요청 메시지를 여러 부분으로 파싱하여 헤더 부분을 조작하기 쉬운 자료 구조에 담는다.
이는 원서버와의 통신과도 같다.
캐싱 서버는 요청한 URL
을 알아내고 그에 해당하는 로컬 사본이 있는지 검사한다.
로컬 복사본은 캐시 서버의 메모리에 저장 되어 있을 수도 디스크나 다른 서버 컴퓨터에 있을 수도 있다.
빠르게 검사하기 위한 다양한 알고리즘이 존재한다.
만약 문서를 로컬에서 가져올 수 없다면 설정에 따라 클라이언트에게 찾을 수 없다는 응답을 보내거나 , 부모 프록시 서버 혹은 원 서버에 정보를 요청한다.
윗단에 정보를 요청 할 떄 TCP 커넥션을 또 맺는다. 그렇기 때문에 프록시 체인이 길 수록 레이턴시가 길어진다고 하였다.
만약 로컬 사본이 존재한다면, 해당 로컬 사본에는 요청한 엔터티 뿐이 아니라 응답 헤더도 포함하고 있기 때문에 올바른 서버 헤더와 함께 반환한다.
이후 캐시된 객체는 얼마나 오랫동안 캐시에 머무르고 있었는지를 알려주는 기록이나 얼마나 자주 사용되었는지 등에 대한 몇몇 메타 데이터를 포함한다.
헤더 | 설명 |
---|---|
Cache-Control | `max-age`, `no-cache`, `no-store`와 같은 캐시 지시문을 지정합니다. |
Expires | 리소스의 만료 날짜를 나타냅니다. |
Last-Modified | 리소스의 마지막 수정 날짜를 나타냅니다. |
ETag | 리소스의 엔터티 태그로, 고유한 식별자를 나타냅니다. |
Age | 캐시에 리소스가 얼마나 오래 머무르고 있는지를 나타냅니다. |
Via | 요청이 거치는 중간 단계를 기록하기 위해 사용됩니다. |
Warning | 메시지의 상태나 변환에 대한 경고 정보를 나타냅니다. |
HTTP
는 캐시가 일정 기간 동안 서버 문서의 사본을 보유 할 수 있도록 한다.
이 기간동안 문서는 신선한 것으로 간주되고 캐시는 서버와의 접촉 없이도 이 문서를 제공 할 수 있다.
하지만 신선도 한계를 넘을 경우 신선하지 않은 것으로 간주되며, 캐시는 그 문서를 제공하기 전에 문서에 어떤 변경이 있었는지 검사하기 위해 서버와의 재검사를 시행한다 .
캐시는 응답을 원 서버에서 온 것 처럼 보이게 하고 싶기 때문에 캐시된 서버 응답 헤더를 토대로 응답 헤더를 생성한다.
캐시는 클라이언트에 맞게 헤더를 조정해야 하는 책임이 있기 때문에
캐시는 헤더를 적절하게 번역하여 보내야 한다.
응답을 보낼 때 캐시 신선도 정보를 삽입하며, 프록시 캐시를 거쳐 갔음을 알려주기 위해 종종 Via
헤더를 포함 시킨다.
캐시가 Date
헤더를 조정해서는 안된다. Date
헤더는 원 서버에서 그 데이터가 최초로 생겨난 일시를 표현한다.
응답 헤더가 준비되면 캐시는 응답을 클라이언트에게 돌려준다.
트랜잭션에 대한 통계를 내린다. 캐시 적중과 부적중 횟수 , 요청 종류 , URL , 그리고 무엇이 일어났는지 등을 로깅한다.
HTTP
는 Cache-Control
과 Expires
라는 특별한 헤더를 이용해 원 서버가 각 문서에 유효기간을 붙일 수 있게 해준다.
해당 헤더들은 사본이 얼만큼 캐시 서버에서 저장 될 수 있는지 기준을 설정한다.
캐시 문서는 해당 기준 이하 일 때 (신선할 때) 는 특별한 검사 없이 리소스를 제공 할 수 있지만
물론 요청 헤더에서 검사되지 않은 리소스의 제공을 거부하는 헤더가 존재하지 않아야 한다.
캐시된 문서가 만료되면 원 서버에서 새로운 유효기간과 함께 사본을 업데이트 한다.
캐시된 문서가 만료 되었다는 것이 꼭 원 서버에 현재 존재하는 것과 다르다는 것을 의미하는 것은 아니다.
다를 수도 있고 같을 수도 있다.
다만 이제 검사 할 시간이 되었다는 것이다.
캐시가 원 서버에게 문서가 변경 되었는지 여부를 물어보는 것을 서버 재검사
라고 한다.
캐시 서버에게 클라이언트의 요청이 생겼을 때
캐시 서버에게 클라이언트는 수정되지 않았는지를 묻는 헤더를 포함했다.
캐시 서버는 조건에 따라 두 가지 작동을 한다. 우선 서버에게 사본이 신선한지를 묻는 것은 동일하다.
이러한 검사를 통해 캐시서버는 항상 충분히 신선한 사본을 제공하거나 에러 메시지 혹은 경고 메시지를 보낼 것이다.
HTTP
의 조건부 메소드는 재검사를 효율적으로 만들어준다.
위에서도 언급했지만 조건부 GET
을 통해 캐시 서버의 신선도를 확인하고, 클라이언트는 항상 신선한 데이터를 얻을 수 있다. (서버에서 사라졌다면 에러 코드를 받겠지만)
조건부 GET 은 GET 요청 메시지에 특별한 조건부 헤더를 추가한다.
If-Modified-Since
: 문서가 주어진 날짜 이후로 수정되었다면 서버로부터 확인을 요청한다. 이 때 프록시 서버는 원 서버와 확인 후 응답 헤더에 마지막 수정일이 적힌 Last-Modified
헤더를 포함시켜 보내준다.If-None-Match
: 클라이언트는 이전에 받았던 문서에 대한 일련 번호(ETag
, 엔터티 태그) 를 기억하고 , 동일한 문서를 요청 할 때 이전에 받았던 것과 같은지 (수정 된 것이 없는지) 를 캐시 서버에게 물어본다. 이 때 클라이언튼느 해당 태그와 같은지를 묻는 방법보다는 , 이전에 받았던 엔터티 태그가 아닐 경우 새로운 문서를 제공해달라고 요청하는 경우가 많다.
두 요청 모두 원 서버와 확인 후 수정된 것이 없다면 304 Not Modified
상태 코드와 함께 엔터티를 반환한다.
만약 수정되었다면 서버는 수정되지 않은 사본을 제공했다는 304 Modified
상태 코드가 아닌 200 OK
상태 코드와 함께 업데이트 된 신선한 사본을 제공할 것이다.
위에서 캐시 서버가 요청에 따라 캐시의 신선도를 확인하는 것을 봤다면
이번엔 원 서버에서 캐시 서버에 사본을 얼만큼 저장 시킬지 알려주는 헤더들을 알아보자
Cache-Control : no-cache
, Cache-Control : no-store
자주 변경이 되는 데이터의 경우 캐시 사본으로 저장시키면 클라이언트는 프록시 서버와 TCP 커넥션 , 찾아보고 .. 없으면 원서버와 다시 또 TCP 커넥션 .. 해야하기 때문에
변경이 자주 되는 데이터의 경우 원서버는 해당 데이터를 캐시 서버에 보낸 후 사본으로 저장 시키지 않도록 할 수 있다.
해당 응답 헤더를 받은 캐시 서버는 받은 데이터를 캐시 서버에 저장하지 않고 클라이언트에게 보내주기만 한다.
Cache-Control : Max-Age = <second>
Max-Age
는 문서가 원 서버로부터 캐시 서버로부터 보낸 후 흐른 시간을 나타낸다. 캐시 서버에 사본이 정해진 시간 만큼만 존재 할 수 있도록 하며 시간은 초 단위로 나눈다.
사본을 저장시키지 않고 싶은 경우엔 Max-Age
를 0으로 설정하기도 한다.
Expires
Expires
헤더는 초 단위가 아닌 실제 만료 날짜를 명시한다.
다만 더 이상 해당 헤더는 사용하지를 않기를 권하는데 이는 서버 마다 부정확한 시계를 가지고 있기 때문에 차라리 초 단위로 만료 기간을 정하는 것을 더 추천한다고 한다.
Cache-Control : Must-Revalidate
Cache-Control : Must-Revalidate
는 캐시 서버가 만료 정보를 엄격하게 따르길 원할 때 사용한다.
원 서버는 캐시 서버가 원서버와의 최초의 재검사 없이는 제공하지 않도록 한다
출처 :
HTTP 완벽 가이드