본문 바로가기
Back-end

캐시(Caching)

by 신재권 2022. 5. 18.

HTTP Caching

웹 사이트와 애플리케이션의 성능은 이전에 가져온 리소스들을 재사용함으로써 현저하게 향상될 수 있다. 웹 캐시는 레이턴시와 네트워크 트래픽을 줄여줌으로써 리소스를 보여주는 데에 필요한 시간을 줄여준다. HTTP 캐싱을 활용하면 웹 사이트가 좀 빠르게 반응하도록 만들 수 있다.

다른 종류의 캐시들

캐싱은 주어진 리소스의 복사본을 저장하고 있다가 요청 시에 그것을 제공하는 기술이다.

웹 캐시가 자신의 저장소 내에 요청된 리소스를 가지고 있다면, 요청을 가로채 원래의 서버로부터 리소스를 다시 다운로드 하는 대신 리소스의 복사본을 반환한다.

모든 클라이언트를 서비스할 필요가 없어지므로 서버의 부하를 완화하고, 클라이언트에 더 가까이 있으므로 성능이 향상된다. 즉 리소스를 회신하는데 더 적은 시간이 들게 된다.

웹 사이트에서 캐싱은 높은 성능을 달성하는데 주요한 요소이다. 반면에 모든 리소스가 영원히 변하지 않는 것은 아니므로 리소스가 변하기 전까지만 캐싱하고 변한 이후에는 더이상 캐싱하지 않는 것이 중요하다.

캐시에 종류에는 크게 사설(Private) 과 공유(Shared) 캐시 두 가지 부류로 분류할 수 있다.

공유 캐시는 한 명 이상의 사용자가 재사용할 수 있도록 응답을 저장하는 캐시이다.

사설 캐시는 한 명의 사용자만 사용하는 캐시이다.

사설 브라우저 캐시

사설 캐시는 단일 사용자가 전용으로 사용한다. 브라우저 캐시는 그 사용자에 의하여 HTTP를 통해 다운로드 된 모든 문서들을 가지고 있다. 이 캐시는 서버에 대한 추가적인 요청 없이 뒤로 가기나 아픙로 가기, 저장, 소스로 보기 등을 위해 방문했던 문서들을 사용할 수 있도록 해준다. 또한 유사한 방법으로 캐시된 컨텐츠의 오프라인 브라우징을 개선시킨다.

공유 프록시 캐시

공유 캐시는 한 명 이상의 사용자에 의해 재사용되는 응답을 저장하는 캐시이다. 예를 들어 , 회사의 ISP는 많은 사용자들을 서비스하기 위해 지역 네트워크 기반의 일부분으로서 우베 프록시를 설치해뒀을 수도 있는데, 그 덕분에 조회가 많이 되는 리소스들은 몇 번이고 재사용되어 네트워크 트래픽과 레이턴시를 줄요준다.

캐싱 동작의 대상

HTTP 캐싱은 선택적이지만 캐시된 리소스를 재사용하는 것은 좋은 일이다.

하지만 HTTP 캐시들은 일반적으로 GET에 대한 응답만을 캐싱하며, 다른 메서드들은 제외된다.

기본 캐시 키(Primary cache key)는 요청 메서드 그리고 대상 URI로 구성된다.(GET 요청만을 대상으로 하므로 URI만 사용되는 경우가 많다.)

일반적인 캐싱 엔트리의 형태는 다음과 같다.

  • 검색(Retrieval) 요청의 성공적인 결과 : HTML 문서, 이미지 혹은 파일과 같은 리소스를 포함하는 GET 요청에 대한 200(OK) 응답.
  • 영구적인 리다이렉트 ; 301(Movend Permanently) 응답
  • 오류 응답 : 404(Not Found) 결과 페이지
  • 완전하지 않은 결과 : 206(Partial Content) 응답
  • 캐시 키로 사용하기에 적절한 무언가가 정의된 경우의 GET 이외의 응답

캐시 엔트리는 요청이 컨텐츠 협상의 대상인 경우, 두 번째 키에 의해 구별되는 여러 개의 저장된 응답들로 구성될 수도 있다.

캐싱 제어

Cache-Control 헤더

Cache-Control HTTP/1.1 기본 헤더 필드는 요청과 응답 양측 모두에 있어 캐싱 메커니즘을 위한 디렉티르를 지정하는데 사용된다. 이 헤더 필드가 제공하는 여러 디렉티브들로 캐싱 정책을 정의하고자 한다면 이 헤더를 사용한다.

캐시하지 않음

캐시는 클라이언트 요청 혹은 서버 응답에 관해서 어떤 것도 저장해서는 안된다는 설정이다. 요청은 서버 측으로 전송되고 전체 응답은 매번 다운로드 된다.

Cache-Control: no-store

캐시하지만 재검증

캐시된 복사본을 사용자에게 릴리즈 하기 전에, 유효성 확인을 위해 원 서버로 요청을 보낸다.

Cache-Control: no-cache

사설 캐시와 공개 캐시

“public” 디렉티브는 응답이 어떤 캐시에 의해서든 캐시되어도 좋다는 것을 가리킨다. 이것은 HTTP 인증, 혹은 보통 캐시 가능하지 않은 응답 상태 코드를 지닌 페이지가 이제 캐시되어야 할 경우 유용할 수 있다.

반면 “private”은 응답이 단일 사용자만을 위한 것이며 공유 캐시에 의해 저장되어서 안된다는 것을 가리킨다. 사설 브라우저 캐시는 이런 경우에 응답을 저장할 수 있다.

Cache-Control: private
Cache-Control: public

만료

여기서 가장 중요한 디렉티브는 “max-age=<seconde>”로 리소스가 유효하다고 판단되는 최대 시간을 말한다. 이 디렉티브는 요청 시간에 상대적이며, Expires가 설정되어 있어도 그보다 우선한다. 변경되지 않을 파일에 대해, 공격적으로 (긴 시간으로) 캐싱할 수 있다. 예를 들어 이미지, CSS 팡리 그리고 자바스크립트 파일과 같은 정적 파일들이다.

Cache-Control: max-age=31536000

검증

“must-revalidate” 디렉티브 사용 시, 캐시는 오래된 리소스를 사용하기 전에 그 상태를 확인하고 만료된 리소스는 사용하지 말아야 한다.

Cache-Control: must-revalidate

Pragma 헤더

Pragma는 HTTP/1.0 헤더이다. Pragma: no-cache는 캐시가 검증을 위해 원래 서버에 요청을 보내도록 강제한다는 점에서 Cache-Control: no-cache와 유사하다. 그러나 Pragma는 HTTP 응답에 대해 명세되지 않았으므로 일반적인 HTTP/1.1 Cache-Control 헤더를 신뢰성 있게 대체할 수 없다.

Cache-Control HTTP/1.1 헤더가 없는 HTTP/1.0 클라이언트와의 하위 호환성을 위한 경우에만 Pragma를 사용해야 한다.

유효성(Freshness)

리소스가 캐시 내에 저장되고 나면, 이론저긍로는 영원히 캐시에 의해 서비스될 수도 있다. 캐시는 유한한 저장 공간을 가지므로 아이템들은 주기적으로 스토리지에서 제거된다. 이런 과정을 캐시 축출(cache eviction)이라고 부른다.

반면 어떤 리소스들은 서버 상에서 변경될 수 있고, 캐시가 갱신되어야 한다. HTTP가 클라이언트-서브 프로토콜 이므로, 리소스가 변경됐을 때 서버는 캐시와 클라이언트에 접근할 수 없다. 서버는 리소스에 대한 만료 시간을 알려줄 수 밖에 없다. 만료 시간 이전에는, 리소스가 유효(Fresh)하다. 만료 시간 이후의 리소스는 실효(Stale)된다.

축출 알고리즘은 대개 실효된 리소스보다 유효한 리소스에 특권을 부여한다. 실효된 리소스는 축출되거나 무시되지 않는다는 것을 알아둬야 한다.

캐시가 실효된 리소스에 대한 요청을 받은 경우, 이 로스가 실제로 아직 유효한지 아닌지를 확인하기 위해 If-None-Match와 함께 요청을 전달한다. 그러면 서버는 요청된 리소스 본문을 전송하지 않고 304(not Modified) 헤더를 돌려보내 대역폭을 절약한다.

Cache-control: max-age=N 헤더가 설정된 경우, 유효 수명은 N과 동일하다. 만약 이 헤더가 없다면, Expires 헤더가 있는지 검사한다. Expires 헤더가 존재한다면, 그것의 값에서 Date 헤더의 값을 뺀 결과가 유효 수명이 된다.

유효성 검사 휴리스틱

원래 서버가 명시적으로 유효성을 지정하지 않으면 휴리스틱으로 유효기간을 추정한다.

이 경우에는 Last-Modified 헤더를 찾는다. 이 헤더가 있다면, 캐시의 유효 수명은 Date 헤더 값에서 Last-modified 헤더 값을 뺀 값을 10으로 나눈 결과가 된다.

만료시간은 다음과 같이 계산된다.

expirationTime = responseTime + freshnessLifetime - currentAge

reponseTime은 브라우저가 응답을 수신한 시간을 말한다.

리비전된(Revved) 리소스

우리가 캐시된 리소스들을 많이 사용할 수록, 웹 사이트의 응답성과 성능은 점점 더 좋아진다. 이것을 최적화 하기 위한 좋은 방법은 만료 시간을 가능한 더 먼 미래로 설정하는 것이다.

이것은 정기적, 자주 업데이트되는 리로스에 대해서는 가능하지만, 드물게 업데이트되는 리소스의 경우에는 문제가 된다. 이런 리소스들을 캐시하면 이득이 크지만, 업데이트하기가 매우 어렵기 때문이다. 캐시한 리소스로부터 최대한 활용되는 리소스들로, 앞서 얘기한 방법 덕분에 이러한 리소스들을 갱신하기 더 어렵게 된다. 대표적으로 각 웹페이지에 포함되고 링크된 기술적인 리소스들이 그렇다. 자바 스크립트와 CSS 파일들의 변경은 드물지만, 그것들이 변경되었을 때는 빠르게 갱신되어야 한다.

캐시 검증

캐시된 문서의 만료 시간이 가까워져오면, 문서가 검증되거나 다시 불러오게 된다. 검증은 서버가 강한 검증 혹은 약한 검증 중 하나라도 제공하는 경우에만 일어날 수 있다.

재검증은 사용자가 리로드 버튼을 누를 경우 촉발된다. 재검증은 캐시된 응답이 Cache-control:must-revalidate 헤더를 포함하고 있는 경우 일반적인 브라우징 중에도 촉발될 수 있다. 또 다른 요인은 Advanced→Cache 환경설정 패널 내에 캐시 검증 환경 설정이다. 거기에는 문서가 로드될 때마다 검증을 강제할 수 있는 옵션이 있다.

ETags

ETag 응답 헤더는 강한 검증으로써 사용될 수 있는 사용자 에이전트에게 있어 불투명한값이다.

브라우저와 같은 HTTP사용자 에이전트가 이 문자열이 무엇을 표현하는지 알 수 없고, 그것의 값이 무엇이 될지를 예측할 수 없다는 것을 의미한다.

Last-Modified

Last-Modified 응답 헤더는 약한 검증으로써 사용될 수 있다.

'Back-end' 카테고리의 다른 글

TDD(Test-Driven-Development) : 테스트 주도 개발  (0) 2022.08.21
쿠키(Cookie)  (0) 2022.05.18
HTTP  (0) 2022.05.18
프로젝트(Spring Boot & JPA)  (0) 2022.01.23
Lombok  (0) 2021.10.06