[HTTP] 7장: 캐시

서정범·2024년 1월 30일
0

HTTP

목록 보기
12/13

본격적으로 캐시에 관련하여 정리를 하기 전에 핵심적인 내용들을 먼저 적어놓고 들어가겠습니다.

캐시(Cache)는 데이터 또는 요청의 결과를 임시로 저장하는 장소를 의미합니다.

캐시의 주요 역할은 다음과 같습니다.

  1. 성능 향상:
    • 캐시를 사용하면 데이터 검색 속도가 향상됩니다. 자주 사용되는 데이터를 캐시에 저장해 두면, 데이터베이스나 원격 서버에 매번 요청을 보내지 않고도 빠르게 데이터를 검색할 수 있습니다.
  2. 네트워크 트래픽 감소:
    • 원격 서버나 데이터베이스에 대한 요청이 줄어들기 때문에 네트워크 트래픽은 감소합니다. 이는 특히 대역폭 사용이 중요한 웹 서비스에서 유리합니다.
  3. 부하 감소:
    • 캐시를 사용하면 원본 데이터를 제공하는 서버의 부하를 줄일 수 있습니다. 데이터베이스 또는 웹 서버의 부담이 줄어들면서 전반적인 성능이 향상됩니다.
  4. 응답 시간 단축:
    • 캐시된 데이터는 원본 데이터 소스보다 훨씬 빠르게 접근할 수 있습니다. 이로 인해 사용자에게 더 빠른 응답 시간을 제공할 수 있습니다.

캐시의 종류는 다음과 같습니다.

  1. 브라우저 캐시:
    • 웹 브라우저는 방문한 웹 페이지의 요소들(이미지, 스타일시트, 자바스크립트 파일 등)을 로컬에 캐시합니다. 이는 재방문 시 페이지 로딩 속도를 향상시킵니다.
  2. 웹 캐시:
    • 웹 서버는 자주 요청되는 웹 페이지나 파일을 캐시하여, 요청이 있을 때 빠르게 응답할 수 있습니다.
  3. CDN 캐시:
    • 콘텐츠 전송 네트워크(Content Delivery Network)는 전 세계 여러 지점에 데이터를 캐시하여, 사용자에게 더 빠른 콘텐츠 제공을 가능하게 합니다.
  4. 메모리 캐시:
    • 시스템 메모리(RAM)를 활용하여 데이터를 캐시하는 방법입니다. 예를 들어, Redis와 같은 인메모리 데이터 스토어가 있습니다.

캐시의 한계는 다음과 같습니다.

  • 데이터 일관성:
    • 캐시된 데이터는 원본 데이터와 일치하지 않을 수 있습니다. 따라서 데이터의 일관성을 유지하기 위한 적절한 캐시 갱신 전략이 필요합니다.
  • 관리 복잡성
    • 캐시 시스템을 효과적으로 관리하려면, 캐시 크키, 만료 정책, 데이터 갱신 등을 적절히 설정해야 합니다.

우선 이와 같이 정리를 해놓고 구체적인 내용들을 살펴봅시다.

1. 캐시의 효과

위에서도 언급했듯이, 캐시를 사용하면 여러 가지 이점을 가져올 수 있습니다.

먼 거리의 통신의 경우 WAN을 사용해야 하고, 해당 통신을 사용할 경우 전파 지연으로 인한 레이턴시를 피할 수 없습니다.

따라서, 이러한 먼 거리의 통신에서 더 높은 성능을 가져오기 위해 클라이언트로부터 더 가까운 웹 캐시 서버를 설치할 수 있을 것입니다.

이렇게 될 경우 클라이언트의 요청이 거쳐가야 되는 트래픽은 더욱 짧아질 것이고, 네트워크 망에서 존재하는 시간 또한 짧아질 것입니다.

이것은 전체 트래픽을 낮추는 효과를 가져올 것이고, 병목 현상을 줄어드는 것을 기대할 수 있습니다.

트래픽이 원 서버로 몰릴 경우, 원 서버에 부하가 걸릴 수 있기 때문에 캐시 서버에 복사된 사본을 저장하여 사용자의 요청들을 분산시킬 수 있습니다.

2. 적중과 부적중

캐시를 사용할 때 한계점에 정리를 해놓은 것 처럼 중요한 것은 데이터의 일관성을 지켜줘야 하는 점입니다.

웹 캐시 서버에서 복사된 사본을 가지고 있고 해당 사본의 출처 리소스가 많이 변경되었을 경우 사용자는 변경된 리소스를 받아야만 합니다.

사용자의 요청이 캐시 서버에 존재하는 리소스일 경우 이것을 캐시 적중(Cache hit)라고 하고, 사본이 없다면 캐시 부적중(Cache miss)라고 합니다.

캐시 부적중이 발생했을 경우, 원 서버로 요청을 보내서 리소스를 받아오면 됩니다.
좀 더 복잡한 경우는 캐시 서버에 사본이 존재하지만 이것이 최신 리소스인지 애매한 경우입니다.

이러한 경우를 위해서 재검사(Revalidation)이라 하는 '신선도 검사'가 존재합니다.

이것을 위해서 대표적으로 사용되는 HTTP 헤더 필드는 If-Modified-Since 헤더 필드입니다. 서버에게 보내는 GET 요청에 이 헤더를 추가하면 캐시된 ㅣ시간 이후에 변경된 경우에만 사본을 보내달라는 의미가 됩니다.

만약, 서버 객체(리소스)가 변경되지 않았다면 서버는 클라이언트에게 작은 HTTP 304 Not Modified 응답을 보낼 것입니다.

서버 객체가 캐시된 사본과 다르다면, 서버는 콘텐츠 전체와 함께 평범한 HTTP 200 OK 응답을 클라이언트에게 보낼 것입니다.

서버 객체가 삭제되었다면, 서버는 404 Not Found 응답을 돌려보내서, 캐시는 사본을 삭제합니다.

캐시 적중률(문서 적중률)은 40% 정도면 웹 캐시로 괜찮은 편이라고 합니다.

전체 트래픽을 고려한 바이트 단위 적중률 측정도 있습니다. 이것은 문서 단위로 적중률을 체크하는 것이 아닌 바이트 단위로 적중률을 체크함으로써 각 문서마다 크기가 달라도 보다 정확하게 트래픽을 체크할 수 있습니다.

여기까지 정리를 해봤다면, 한가지 궁금한 점이 생길 수 있다.

클라이언트는 요청으로부터 받은 리소스가 복사된 사본인지, 본래 객체인지 구별할 수 있을까?

이것을 구분하려면 다음과 같은 헤더들을 이용할 수 있겠습니다.

  • Date 응답 헤더: 응답의 Date헤더 값을 현재 시각과 비교하여, 응답의 생성일이 더 오래되었다면 클라이언트는 응답이 캐시된 것임을 알 수 있습니다.
  • Age 응답 헤더: 응답이 얼마나 오래되었는지 말해줍니다.

3. 캐시 토폴로지

캐시도 프록시와 마찬가지로 계층 구조를 이룰 수 있는데, 이것은 굉장히 자주 사용되는 구조입니다.

대표적으로 운영 체제의 CPU와 메모리 구조에서도 캐시 메모리가 계층 구조로 이루어져 있습니다.

캐시 서버도 이와 비슷한 목적으로 사용되며 확장 버전 정도로 이해할 수 있습니다.

  • 가까운 캐시 서버의 경우, 작은 용량을 사용하면서 작은 클라이언트 그룹에게 제공된다. 속도가 빠르다.
  • 먼 캐시 서버의 경우, 큰 용량을 사용하면서 여러 리소스들을 관리하며 여러 캐시 서버가 해당 캐시 서버에 접근하여 리소스를 받아올 수 있습니다. 클라이언트로부터 거리가 멀어서 접근 시간은 더 오래 걸립니다.

캐시망 안에서의 콘텐츠 라우팅을 위해 설계된 캐시들은 다음에 나열된 일들을 모두 할 수 있을 것입니다.

  • URL에 근거하여, 부모 캐시와 원 서버 중 하나를 동적으로 선택합니다.
  • URL에 근거하여 특정 부모 캐시를 동적으로 선택합니다.
  • 부모 캐시에게 가기 전에, 캐시된 사본을 로컬에서 찾아봅니다.
  • 다른 캐시들이 그들의 캐시된 콘텐츠에 부분적으로 접근할 수 있도록 허용하되, 그들의 캐시를 통한 Internet transit은 허용하지 않습니다.

4. 캐시 처리 단계

HTTP GET 메시지 하나를 처리하는 기본적인 캐시 처리 절차는 일곱 단계로 이루어져 있습니다.

  1. 요청 받기: 캐시는 네트워크로부터 도착한 요청 메시지를 읽습니다.
  2. 파싱: 캐시는 메시지를 파싱하여 URL과 헤더들을 추출합니다.
  3. 검색: 캐시는 로컬 복사본이 있는지 검사하고, 사본이 없다면 사본을 받아옵니다.(그리고 로컬에 저장합니다.)
  4. 신선도 검사: 캐시는 캐시된 사본이 충분히 신선한지 검사하고, 신선하지 않다면 변경사항이 있는지 서버에게 물어봅니다.
  5. 응답 생성: 캐시는 새로운 헤더와 캐시된 본문으로 응답 메시지를 만듭니다.
  6. 발송: 캐시는 네트워크를 통해 응답을 클라이언트에게 돌려줍니다.
  7. 로깅: 선택적으로, 캐시는 로그파일에 트랜잭션에 대해 서술한 로그 하나를 남깁니다.

추가적인 개념들을 정리해보자면 다음과 같습니다.

우선, 3단계인 검색 단계에서 로컬 복사본이 있는지 검사하는 것은 본문만을 포함하는 것이 아닙니다. 캐시된 객체서버 응답 본문원 서버 응답 헤더를 포함하고 있으므로, 캐시 적중 동안 올바른 서버 헤더가 반환될 수 있습니다.

5단계인 응답 생성에서 캐시는 클라이언트에 맞게 헤더를 조정해야 하는 책임이 있습니다.

클라이언트의 요구 사항에 맞춰서 헤더를 적절하게 번역해주고, 필요한 경우 캐시 신선도 정보를 삽입하거나 Via 헤더 혹은 Date 헤더를 추가하여 캐시 서버를 사용했음을 알릴 수 있습니다.

5. 사본을 신선하게 유지하기

그렇다면, 캐시 서버는 문서가 만료되었다는 사실은 무엇을 통해서 알 수 있을까?

HTTP는 Cache-ControlExpires라는 특별한 헤더들을 이용해서 원 서버가 각 문서에 유효기간을 붙일 수 있게 해줍니다.

  • Cache-Control: max-age: max-age 값은 문서의 최대 나이를 정의한다.
  • Expires: 절대 유효기간을 명시한다.

Expires 응답 헤더는 deprecated 되었습니다. HTTP를 설계한 사람들은, 많은 서버가 동기화되어 있지 않거나 부정확한 시계를 갖고 있기 때문에, 만료를 절대시각 대신 경과된 시간으로 표현하는 것이 낫다고 판단했기 때문입니다.

위와 같은 것들을 이용해서 검사를 수행할 수 있고, 만약 복사된 사본이 재검사를 해야하는 경우 어떻게 동작하는지 알아보자.

복사된 사본의 재검사 요청의 결과의 경우 삭제된 경우를 제외하고 두 가지가 존재합니다.

  • 변경되었거나,
  • 변경 안되었거나

변경 되었을 경우에는 새로운 객체를 받아올 필요가 있지만 변경되지 않았다면 굳이 객체를 받아오면서 불필요한 오버헤드를 만들 필요가 없습니다.

그래서 여기서 등장하는 것이 조건부 메서드 요청입니다.

  • If-Modified-Since: <date>: 만약 문서가 주어진 날짜 이후로 수정되었다면 요청 메서드를 처리합니다.
  • If-None-Match: <tags>: 마지막 변경된 날짜를 맞춰보는 대신, 서버는 문서에 대한 일련번호와 같이 동작하는 특별한 태그(ETag)를 제공할 수 있습니다.

가장 흔히 쓰이는 캐시 재검사 헤더는 If-Modified-Since이고, 이것은 Last-Modified 헤더와 함께 동작합니다.

  • 문서가 변경되었다면, 날짜 비교 조건은 참일 것이고 GET 요청은 평범하게 성공합니다. 새 문서가, 새로운 만료 날짜와 그 외 다른 정보들이 담긴 헤더들과 함께 캐시에게 반환됩니다.
  • 문서가 변경되지 않아서, 날짜 비교 조건이 거짓이고 서버는 작은 304 Not Modified 응답 메시지를 클라이언트에게 돌려줍니다. 효율을 위해 본문은 보내지 않습니다. 응답은 필요한 헤더들을 포함하지만, 원래 돌려줘야 할 것에서 갱신이 필요한 것들만 보내줍니다.

위의 동작과 같이 캐시는 변경되지 않는 사본의 업데이트는 헤더만 이루어지지고, 전송 과정에서 불필요한 오버헤드는 최대한 피함으로써 빠르게 동작하게 합니다.

하지만, 해당 방식에는 단점이 존재합니다.

  • 어떤 문서는 일전 시간 간격으로 다시 쓰여지지만 실제로는 같은 데이터를 포함할 수 있다.
  • 변경 사항이 매우 적어서 굳이 다시 읽을 필요가 없을 수 있다.
  • 어떤 서버들은 최근 변경 일시를 정확하게 판별 할 수 없다.
  • 1초보다 작은 간격으로 갱신되는 문서를 제공하는 서버들에게는, 변경일에 대한 1초의 정밀도는 충분하지 않을 수 있다.

이러한 단점들은 ETag를 사용하는 If-None-Match를 사용하면서 해결할 수 있습니다.

캐시의 검사기에는 두 가지 종류가 있습니다.

  • '약한 검사기(weak validator)': 콘텐츠가 조금 변경되었더라도 허용해줍니다.
  • '강한 검사기(strong validator)': 콭텐츠의 변경을 허용하지 않고, 콘텐츠가 바뀔 때마다 바뀝니다.

약한 검사기의 경우 W/ 접두사를 활용합니다.

ETag: W/"v2.6"
If-None-Match: W/"v2.6"

언제 엔터티 태그를 사용하고 언제 Last-Modified 일시를 사용하는 것일까요?

그것은 서버가 어떠한 값을 반환했는지에 따라 달라집니다.

만약, 서버가 엔터티 태그만 반환했을 경우 엔터티 검사기로 진행합니다.
하지만, Last-Modified-Since만 반환했을 경우 해당 검사기를 사용합니다.

두 방식 모두 사용이 가능하다면, HTTP/1.0과 HTTP/1.1 캐시 모두 적절히 응답할 수 있도록 클라이언트는 각각을 위해 두 가지의 재검사 정책을 모두 사용해야 합니다.

6. 캐시 제어

HTTP는 문서가 만료되기 전까지 얼마나 오랫동안 캐시될 수 있게 할 것인지 서버가 설정할 수 있는 여러 가지 방법을 정의합니다. 우선순위대로 나열해보면 서버는,

  • Catch-Control: no-store 헤더를 응답에 첨부할 수 있습니다.
  • Catch-Control: no-cache 헤더를 응답에 첨부할 수 있습니다.
  • Catch-Control: must-revalidate 헤더를 응답에 첨부할 수 있습니다.
  • Catch-Control: max-age 헤더를 응답에 첨부할 수 있습니다.
  • Expires 헤더를 응답에 첨부할 수 있습니다.
  • 아무 만료 정보도 주지 않고, 캐시가 스스로 체험적인(휴리스틱) 방법으로 결정하게 할 수 있습니다.

하나씩 살펴봅시다.

우선 'no-store'가 표시된 응답은 캐시가 그 응답의 사본을 만드는 것을 금지합니다.

'no-cache'로 표시된 응답은 로컬 캐시 저장소에 저장될 수 있습니다. 다만 먼저 서버와 재검사를 하지 않고서는 캐시에서 클라이언트로 제공될 수 없을 뿐입니다.

Cache-Control: max-age 헤더는 신선하다고 간주되었던 문서가 서버로부터 온 이후로 흐른 시간이고, 초로 나타냅니다. 0으로 설정하여 캐시하지 않거나 혹은 매 접근마다 리프레시 하도록 요청할 수 있습니다.

Expires 응답 헤더는 초 단위의 시간 대신 실제 만료 날짜를 명시합니다. deprecated 되었습니다.

Cache-Control: must-revalidate 응답 헤더는 캐시가 이 객체의 신선하지 않은 사본을 원 서버와의 최초의 재검사 없이는 제공해서는 안 됨을 의미합니다.

캐시 설정을 느슨하게 해주는 지시어들도 존재하는데 그러한 것들이 있다는 것만 염두해두고 넘어갑시다.

이후 내용

이후에 웹 서버 종류별로 캐시를 설정하는 방법에 대해서 간략하게 제시하고 있습니다.

그리고 나이와 신선도 수명 계산하는 알고리즘에 대해서 소개하고 있습니다.

이 부분은 다루지 않겠습니다.

참고한 자료

  • HTTP 완벽 가이드
profile
개발정리블로그

0개의 댓글