네트워크/HTTP

[HTTP] 캐시와 조건부 요청

ReBugs 2024. 2. 12.

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


캐시를 사용하지 않는다면, 서버의 데이터가 변경되지 않았어도 클라이언트는 서버의 데이터를 다운로드 받아야 한다.

때문에 브라우저 로딩 속도가 느려진다. 이는 성질 급한 사용자가 참지 못하고 해당 사이트를 이탈할 수도 있다.

이는 SEO에 안좋은 영향을 미치기 때문에 장기적으로 사이트에 안좋은 영향을 끼칠 수 있다.

또한 인터넷 네트워크는 느리고 비싸기 때문에 캐시를 필수적으로 사용해야 한다.

 

캐시 기본 동작

캐시 시간 설정

 

브라우저에서 GET /star.jpg 첫번째 요청을 보내면, 서버는 HTTP 헤더(0.1M) + HTTP 바디=star.jpg 이미지(1.0M)를 담아 응답을 보낸다.

중요한 점은 HTTP 헤더에 cache-control로 캐시 유효 시간을 설정해서 응답한다는 것이다.

예시는 최대 캐시 유효 시간을 60초로 설정하였다.

 

캐시 유효 시간이 만료 되기 전에 다시 서버에 요청하면 아래의 그림과 같이 서버에 요청하기 전 브라우저 캐시에 요청을 보내고 캐시 유효 시간을 검증한다.

 

캐시 유효시간이 유효하다면 캐시에서 직접 조회하여 브라우저에 이미지를 표시한다.

 

캐시 시간 초과

만약 캐시 유효 시간을 검증했는데 유효 시간을 초과했다면 서버로 요청하게 된다.

서버는 이전과 동일하게 최대 캐시 유효시간을 설정하여  HTTP 헤더(0.1M) + HTTP 바디=star.jpg 이미지(1.0M)를 담아 응답을 보낸다. 

 

또한 클라이언트 웹 브라우저는 이전과 동일하게 브라우저 캐시에 60초 유효 상태로 응답 결과를 저장한다.

 

단순히 캐시의 시간만 설정하는 것은 비효율적일 수도 있다.

캐시 유효 기간이 지났지만, 서버의 데이터가 바뀌지 않았을 경우, 불필요한 데이터 다운로드가 있을 수 있기 때문이다.

이는 검증 헤더로 극복이 가능하다.

 

 

검증 헤더와 조건부 요청

검증 헤더 : 캐시 데이터와 서버 데이터가 같은지 검증하는 데이터

  1. Last-Modified
  2. ETag

 

조건부 요청 헤더 : 검증 헤더로 조건에 따른 분기시 사용

  • If-Modified-Since: Last-Modified를 사용
    If-Modified-Since <->If-Unmodified-Since
  • If-None-Match: ETag 사용
  • 조건이 만족하면 200 OK
  • 조건이 만족하지 않으면, 304 Not Modified

 

Last-Modified

캐시의 유효 시간이 초과된 이후에도 서버의 데이터가 동일하다면, 이미 다운로드된 데이터를 다시 다운로드 받지 않아도 된다.

브라우저 캐시와 서버의 데이터가 동일한지 아닌지를 판단할 수 있는 방법은 검증 헤더를 추가하는 것이다.

 

먼저 브라우저 캐시에 아무것도 없다고 가정하고, 서버에 요청을 보낸다.

그러면 서버는 헤더에 Last-Modified(데이터가 마지막에 수정된 시간)을 설정해서 클라이언트에 데이터를 전송한다.

 

클라이언트는 캐시 유효 시간과 데이터 최종 수정일을 함께 응답 결과를 캐시에 저장한다. 

 

Last-Modified - 캐시 시간 초과

클라이언트가 캐시 만료 후 요청을 보낸다.

요청을 보낼 때 헤더에 'if-modified-since:데이터 최종 수정일'을 붙여 서버에 요청을 보낸다.

데이터 최종 수정일은 GMT 기준으로 작성
Last-Modified: Thu, 04 Jum 2020 07:19:24 GMT

 

서버는 요청 받은 헤더의 if-modified-since 값과 데이터 최종 수정일을 비교하여 데이터가 바뀌었는지 검증한다.

 

만약 캐시의 데이터 최종 수정일과 서버의 데이터 최종 수정일이 같다면(데이터의 변경이 발생하지 않았다면)

(만약 데이터의 변경이 발생하였다면 200 상태 코드를 전달하게 된다. 따라서 서버의 데이터를 다운로드 받는다.)

 

서버는 데이터가 변경되지 않았다는 의미로 HTTP Body 없이, HTTP 헤더에 304 Not Modified를 전송한다.
HTTP Body 없이 HTTP Header 부분만 전송하기 때문에 라이트한 데이터를 보낸다.

 

클라이언트는 서버로부터 304 Not Modified를 응답받았으므로 캐시 데이터(응답 결과)를 재사용하고 헤더 데이터를 갱신한다.

300번대 메시지는 리다이렉트 응답이다.

따라서 캐시로 리다이렉트 된다.

 

클라이언트의 웹 브라우저와 서버의 데이터가 같으니 캐시에서 데이터를 조회하여 사용한다.

 

흐름을 정리하면 아래와 같다.

  1. 클라이언트가 서버에 데이터를 최초 요청한다.
  2. 서버는 cache-control, last-modified를 헤더에 붙여 캐시의 유효 기간, 데이터 최종 수정일을 설정하여 응답을 보낸다.
  3. 브라우저 캐시에 유효 기간과 데이터 최종 수정일과 함께 응답 결과를 저장한다.
  4. 클라이언트가 캐시의 유효기간이 만료된 데이터를 요청하면, 요청 헤더의 if-modified-since에 last-moidifed(데이터 최종 수정일)값을 설정하여 서버에 요청을 보낸다.
  5. 서버는 if-modified-since 값과 데이터의 최종 수정일을 비교하여 데이터 변경이 있는지 검증한다.
  6. 데이터 변경이 없었다면, 서버는 헤더 메타 정보, 304 Not Modified 를 붙여 응답을 보낸다. (메시지 바디 포함 X)
  7. 클라이언트가 304 응답을 받으면, 캐시의 메타 정보(캐시 유효기간 등)을 갱신하고, 캐시에 저장된 데이터를 조회하여 사용한다.

 

서버

  • 캐시가 만료되도 서버의 데이터가 변경되지 않았으면, 서버는 304 Not Modified + 헤더 메타 정보만 응답(HTTP bodyX)

 

클라이언트

  • 클라이언트는 서버가 보낸 응답 헤더 정보로 캐시의 메타 정보를 갱신(캐시 유효 기간도 갱신)
  • 클라이언트는 캐시에 저장된 데이터 재활용
    -> 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드하기 때문에, 매우 실용적

 

If-Modified-Since -> 데이터가 변경되었을 때와 변경되지 않았을 때

데이터 미변경 예시

  • 캐시: 2020년 11월 10일 10:00:00 vs 서버: 2020년 11월 10일 10:00:00
  • 304 Not Modified, 헤더 데이터만 전송한다. (body X)
  • 따라서 전송 용량 0.1MB (헤더 0.1MB)

데이터 변경 예시

  • 캐시: 2020년 11월 10일 10:00:00 vs 서버: 2020년 11월 10일 11:00:00
  • 200 OK, 모든 데이터 전송 (body O)
  • 따라서 전송 용량 1.1MB (헤더 0.1MB, 바디 1MB)

 

Last-Modified, If-Modified-Since의 단점

  • 1초 미만 단위로 캐시 조정이 불가능하다. (최근 갱신 일자의 단위가 초까지임)
  • 날짜 기반의 로직을 사용해야 한다

 

따라서 데이터를 수정해서 날짜가 달라졌지만, 실질적으로 같은 데이터를 수정해 결과가 똑같은 경우, 서버에서 별도의 캐시 로직을 관리하고 싶은 경우 예) 스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지하고 싶은 경우

사용을 해야 한다.

 

ETag

ETag (Entity Tag)

  • 캐시용 데이터에 임의의 고유한 버전 이름을 달아두는 구조
    예) ETag: "v1.0", ETag: "a2jiodwjekjl3" , 버전으로 태그를 관리할 수도 있고, Hash 값으로 할 수도 있다.
  • 데이터가 변경되면, 이 이름을 바꾸는 것을 포함해 변경한다. (Hash를 다시 생성한다)
    예) ETag: "aaaaa" -> ETag: "bbbbb"
  • 단순하게 ETag만 보내서 깂이 같으면 유지하고, 다르면 다시 받는다.
  • If-Match <-> If-None-Match

클라이언트가 서버에 데이터를 요청한다.

서버는 ETag와 함께 데이터를 클라이언트에 보내준다.

 

응답 결과를 브라우저 캐시에 저장한다.

 

ETag - 캐시 시간 초과

설정하였던 캐시가 시간 초과가 될 경우, if-None-Match: ETag 로 서버에 데이터가 아직 변함 없는지 확인 요청을 보낸다.

 

데이터가 아직 수정되지 않았다면 304 Not Modified 상태 코드를 보낸다.

(만약 데이터가 수정되었다면, 200 OK 상태코드를 받게되고, 서버로부터 데이터를 다운로드 받는다. Body는 당연히 있다.)

이때 HTTP Body가 없이 헤더만 보낸다.

 

서버가 보낸 헤더를 클라이언트가 받고, 클라이언트는 브라우저 캐시에 있는 데이터를 재사용하게 되고 헤더 데이터를 갱신한다.

304 상태코드는 리다이렉트이다.

따라서 캐시된 페이지로 리다이렉트 된다.

 

단순하게 ETag만 서버에 보내서 같으면 유지하고, 다르면 다시 받는다.

캐시 제어 로직을 서버에서 완전하게 관리한다.

클라이언트는 단순히 이 값을 서버에 제공(클라이언트는 캐시 메커니즘을 모름)

예시)
서버는 배타 오픈 기간인 3일 동안 파일이 변경되어도, ETag를 동일하게 유지한다.
애플리케이션 배포 주기에 맞춰 ETag를 모두 갱신

이렇게 ETag와 If-None-Match를 사용할시, 클라이언트의 입장에서는 캐시 메커니즘이 완전히 블랙박스가 되어버린다.

 

프록시 캐시

클라이언트와 거리가 먼 서버로 요청과 응답은 당연히 느릴 수 밖에 없다.

이를 극복하기 위해 프록시 캐시 서버를 사용한다.

proxy cache라는 서버를 도입해서 브라우저(클라이언트의)가 원(origin) 서버가 아닌 proxy cache 서버를 거쳐오도록 만든다.

 

DNS에 요청을 하면 원 서버로 바로 가는 것이 아닌 프록시 서버로 요청을 1차적으로 보내게 되는 것이다.

원 서버는 미국에 있지만, 프록시 서버는 한국 아니면 미국보다 가까운 곳에 위치하기 때문에 훨씬 빠르다.

  • private cache: 내 로컬 환경이나 브라우저에 저장되는 캐시
  • public cache: 여러 유저가 공용해서 사용하는 캐시
CDN Contents Delivery Network 콘텐츠 전송 네트워크

지리적으로 분산된 여러개의 서버 네트워크를 두어  병목 현상을 방지하고 효율적인 네트워크 이동을 제공한다.
자주 사용하는 파일들을 Caching 하여 웹 서버의 부하를 줄인다.
Streaming 기술을 제공하여 실시간으로 많은 사용자가 원하는 콘텐츠를 전송해준다.
서버의 트래픽을 덜어주어 비용을 감소하는 효과가 있고, 공격 트래픽을 완화할 수 있어 Dos 공격에 대해서 어느정도 보호해줄 수 있다.

 

캐시와 조건부 요청 헤더

캐시의 제어와 관련된 헤더들은 아래와 같다.

  • Cache-Control: 캐시 제어
  • Pragma: 캐시 제어(하위 호환)
  • Expires: 캐시 유효 기간(하위 호환)

 

Cache-Control - 캐시 지시어(directives)

  • Cache-Control: max-age - 캐시 유효 시간, 초 단위로 작성
  • Cache-Control: no-cache - 데이터는 캐시해도 되지만, 항상 프록시 서버가 아닌 원(origin) 서버에 검증하고 사용
    (로컬 캐시에 있는 정보가 바뀌었는지 여부를 항상 서버에 검증을 받고 사용)
  • Cache-Control: no-store - 데이터에 민감한 정보가 있으므로 저장하면 안될때 사용.
    (메모리에서 사용하고 최대한 빨리 삭제)
  • Cache-Control: public : 응답이 public 캐시에 저장되어도 됨
  • Cache-Control: private : 응답이 해당 사용자만을 위한 것임, private 캐시에 저장해야 함(기본값)
  • Cache-Control: s-maxage : 프록시 캐시에만 적용되는 max-age
  • Age: 60 (HTTP 헤더) : 오리진 서버에서 응답 후 프록시 캐시 내에 머문 시간(초)

 

Pragma - 캐시 제어(하위 호환)

  • Pragma: no-cache - Cashe-Control의 no-cach와 기능 같음
  • HTTP 1.0 이하 버전일 때 사용

 

Expires - 캐시 만료일 지정(하위 호환)

  • expires: Mon, 01 Jan 1990 00:00:00 GMT
  • 캐시 만료일을 정확한 날짜로 지정할 수 있다.
  • HTTP 1.0부터 사용됨, 지금은 더 유연한 Cache-Control: max-age를 권장
  • 현재 버전에서 Cache-Control: max-age와 함께 사용하면 Expires는 무시된다.

 

캐시 무효화

캐시 데이터를 사용하면 안될 상황이 있다. 예를 들어 나의 통장 잔고 조회등이 있다.

따라서 캐시를 무효화할 필요가 있다.

이때 아래와 같이 캐시 무효화를 해주는 헤더 정보를 입력하면 캐시를 무효화할 수 있다.

  • Cache-Control: no-cache, no-store, must-revalidate 
  • Pragma: no-cache -> http 1.0 이하를 위해

 

  • Cache-Control: no-cache : 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용(이름에 주의!)
  • Cache-Control: no-store : 데이터에 민감한 정보가 있으므로 저장하면 안됨
    (메모리에서 사용하고 최대한 빨리 삭제)
  • Cache-Control: must-revalidate : 캐시 만료후 최초 조회시 원 서버에 검증해야함
    원 서버 접근 실패시 반드시 오류가 발생해야함 - 504(Gateway Timeout)
    캐시 유효 시간이라면 캐시를 사용함
  • Pragma: no-cache : HTTP 1.0 하위 호환

 

no-cache vs must-revalidate

no-cache는 프록시 캐시를 거쳐 원 서버에 검증을 요청하게 된다.

 

하지만 프록시 캐시와 원 서버가 통신 상태가 불량하다면, 오류를 발생하는 것이 아닌 프록시 캐시에 있는 캐시를 클라이언트로 응답하게 된다.

 

반면에 must-revalidate 는 프록시 캐시와 원 서버의 통신이 불량하다면 오류를 발생시킨다.

 

'네트워크 > HTTP' 카테고리의 다른 글

[HTTP] 일반 헤더  (1) 2024.02.11
[HTTP] 상태코드  (1) 2024.02.10
[HTTP] HTTP 메서드 활용  (1) 2024.02.09
[HTTP] HTTP 메서드  (1) 2024.02.08
[HTTP] HTTP 기본  (1) 2024.02.07

댓글