섹션 8: HTTP 헤더2 - 캐시와 조건부 요청

MineeHyun·2024년 8월 1일
0

캐시 기본 동작

캐시가 없으면:

  • 똑같은 데이터를 보내려고 해도 계속 네트워크를 통해서 데이터를 다운로드 받아야 한다.
    • 네트워크는 (상대적으로) 느리고 비쌈!
    • => 브라우저 로딩 속도가 느려진다.
    • => 사용자 경험에 좋지 않다.

캐시를 적용하자:

  • 캐시를 적용하면 일어나는 일
    • 웹 브라우저에서 요청을 보냄
    • 응답 메세지 헤더에 cache-control을 넣음. cache-control: max-age=60과 같이 넣어서 캐시의 유효기간을 설정할 수 있음. (60초 동안 캐시가 유효하다는 뜻)
    • 최초 요청에 대한 응답에는 요청된 파일을 그대로 내림
    • 웹 브라우저에는 캐시 저장소가 있다. 브라우저는 응답에서 받은 파일을 캐시 저장소에 넣음.
    • 두 번째 요청이 왔을 때 캐시 저장소를 먼저 뒤진다. 캐시가 있으면 캐시에서 파일을 바로 가져온다.
  • 캐시 덕분에:
    • 캐시 가능 시간 동안에는 같은 데이터를 요청하는 거라면 네트워크를 사용하지 않아도 된다.
    • = 비싼 네트워크 사용량을 줄일 수 있다.
    • 브라우저 로딩 속도가 (상대적으로) 매우 빨라진다. -> 좋은 사용자 경험!
  • 만약 캐시 유효 시간이 초과되면:
    • 또 요청을 보내고 응답을 받아온다. 기존 캐시를 지우고 새 캐시를 넣는다. (캐시 덮어쓰기, 갱신이 일어남)
    • 네트워크 다운로드가 발생한다.

검증 헤더와 조건부 요청1

캐시 유효 시간이 초과해서 다시 요청해야 할 때

(1) 서버에서 기존 데이터를 변경한 경우:

  • 새 데이터를 다운 받아서 캐시를 갱신해서 사용하는 게 옳다

(2) 기존 데이터를 변경하지 않은 경우: 꼭 처음부터 다 다시 다운로드 받을 필요가 있을까?

  • 재사용할 수 있다. (그래도 된다: 데이터가 바뀌지 않았다는 것을 클라이언트가 확인할 수 있다면)
  • 이것을 확인할 수 있는 방법이 필요하게 된다 (!)

검증 헤더 추가

  • 응답 메세지에 Last-Modified 헤더 필드를 추가: 이 데이터가 마지막으로 수정된 시간을 넣어둠 (UTC 표기법으로 작성)
  • 클라이언트가 요청 메세지를 보낼 때 캐시에 저장돼있는 데이터의 Last-Modified 필드의 값을 꺼내서 if-modified-since (조건부 요청)라는 헤더 필드에 넣어서 보낸다. (캐시가 가지고 있는 데이터 최종 수정일을 적어서 보낸다.)
    • 서버는 이 값을 받아서 가지고 있는 데이터의 최종 수정일과 비교:
    • (1) 수정이 안 되었으면 (그대로 써도 괜찮으면): 304 Not Modified, cache-control과 Last-Modified를 넣어서 응답을 보낸다. HTTP 바디는 없음!!
      • 클라이언트는 이걸 받아서 cache-control 값 (캐시의 메타 데이터) 변경하고 데이터를 캐시에서 조회해서 사용한다.
      • 네트워크 다운로드가 발생하기는 하지만 용량이 적은 헤더 정보만 다운로드 하면 됨

질문: 300번대 코드는 Location 필드가 있고 응답을 보내면 클라이언트가 자동으로 Location 필드의 URI로 가도록 되어 있지 않... 나? 304는 Location이 없어도 되는 걸까? (어차피 캐시에 대한 거라는 것을 알고 있으니까?)
Location, 304 Not Modified

The HTTP 304 Not Modified client redirection response code indicates that there is no need to retransmit the requested resources. This response code is sent when the request is a conditional GET or HEAD request with an If-None-Match or an If-Modified-Since header and the condition evaluates to false. It is an implicit redirection to a cached resource that would have resulted in a 200 OK response if the condition evaluated to true. 출처
해석: If-None-Match, If-Modified-Since가 false면 (데이터의 생성일자와 클라이언트가 적어 보낸 캐시의 생성 일자가 같으면) 304, true면 200을 return함. 304일 때는 캐시 리소스로의 묵시적 리다이렉션을 전제하고 있음.

결론:
(1) 300번대 코드라고 해서 무조건 Location 필드가 필요한 것은 아니다. 300번대 코드와 Location 필드가 조합돼서 리다이렉션으로 기능하는 것임.
(2) 304는 Location 필드를 필요로 하지 않는 코드다. 캐시로의 리다이렉션을 전제하고 있기 때문에!

검증 헤더와 조건부 요청2

  • 검증 헤더: 캐시 데이터와 서버 데이터가 같은지 검증하는 데이터
    • Last-Modified, Etag
  • 조건부 요청 헤더
    • 검증 헤더로 조건에 따른 분기
    • If-Modified-Since: Last-Modified와 함께 사용
    • If-None-Match: Etag와 함께 사용
    • True이면 200 OK, false이면 304 Not Modified (캐시로 가면 됨!)
    • If-Modified, If-None-Match 처럼... 집중하는 상황이 false가 되도록 설계한 이유: 실패했으니까 200 OK가 아님! (엄청나게 논리적임;)

Last-Modified와 If-Modified-Since의 단점:

  • 1초 미만 단위의 캐시 조정이 불가능함
  • 날짜 기반의 로직을 사용함 (정해진 로직)
  • 데이터를 수정해서 날짜 값이 달라졌지만... a -> b -> a라서 데이터 결과가 똑같은 경우
    • 회피할 수 없음 (날짜는 달라졌으니까 True)
  • 서버에서 별도의 캐시 로직을 관리하고 싶은 경우

ETag (Entity Tag), If-None-Match

  • 캐시용 데이터에 임의의 고유한 버전 이름을 달아둠
    • (예) ETag: "v1.0", ETag: "데이터에 대한 해시값"
  • 데이터가 변경되면 이 이름을 바꿔서 변경함 (해시를 다시 생성)
  • ETag만 보내서 같으면 유지, 다르면 다시 받깅
  • ETag의 메카니즘
    • 응답에 ETag를 붙여서 보냄 -> 캐시에 저장할 때 ETag도 같이 저장해 둔다.
    • 요청을 보낼 때 If-None-Match에 ETag를 붙여서 보냄. (가지고 있는 ETag가 있으면)
    • If-None-Match=False면 서버가 클라이언트에 응답을 보낼 때 304 Not Modified, HTTP 바디 없음으로 보낸다
    • 응답을 받으면 클라이언트는 캐시의 메타데이터를 갱신
  • ETag의 장점
    • 캐시 제어 로직을 서버에서 완전히 관리: 클라이언트는 캐시 메커니즘을 전혀 모름

캐시와 조건부 요청 헤더

캐시 제어 헤더

  • Cache-Control (제일 중요)
    • :max-age
      • 캐시 유효 시간, 초 단위로 작성
    • :no-cache
      • 데이터는 캐시해도 되지만, 항상 오리진 서버에 검증하고 사용 (중간 프록시 서버들한테 물어보는 건 안됨)
      • If-Modified-Since나 If-None-Match를 항상 보내고 써라
      • 검증 없이 사용해도 되는 유효시간이 없음
    • :no-store
      • 데이터에 민감한 정보가 있으므로 저장하면 안됨 (메모리에서 사용하고 최대한 빨리 삭제)
  • Pragma: no-cache
    • HTTP 1.0 하위 호환 (잘 사용 안함, 호환성을 위해서 사용하기도 함)
  • Expires: 캐시 만료일 지정 (하위 호환)
    • 캐시 만료일을 정확한 날짜로 지정
    • HTTP 1.0부터 사용
    • 지금은 Cache-Control: max-age 권장 (훨씬 유연함)
    • 요즘 버전에서는 Cache-Control: max-age와 함께 사용하면 Expires는 무시
  • 조건부 요청 헤더
    • If-Match, If-None-Match: 검증에 ETag 값 사용
    • If-Modified-Since, If-Unmodified-Since: 검증에 Last-Modified 값 사용

프록시 캐시

  • CDN 서비스

  • 원 서버에 직접 요청해서 데이터를 받으면 너무 느리니까

    • 가까운 곳에 있는 프록시 캐시 서버를 만들고 요청이 들어오면 이 서버가 요청을 먹음
    • 프록시 캐시 서버가 오리진 서버에 요청을 보냄 -> 데이터를 받아옴 -> 캐시
    • 프록시 캐시 서버랑 가까운 곳에서 요청을 보낸 클라이언트들한테는 프록시 캐시 서버가 응답을 보냄
    • 프록시 캐시 서버가 들고 있는 캐시를 public 캐시, 클라이언트가 들고 있는 캐시를 private 캐시라고 한다
  • Cache-Control: public

    • 응답이 public에 저장돼도 됨 (공용으로 사용하는 이미지, 비디오, ...)
  • Cache-Control: private

    • 응답이 해당 사용자만을 위한 것임. private 캐시에 저장해야 함. (기본값)
    • 유저 로그인 정보 같은 것...
  • Cache-Control: s-maxage

    • 프록시 캐시에만 적용되는 max-age
  • Age: 60 (HTTP 헤더)

    • 오리진 서버에서 응답 후 프록시 캐시 내에 머문 시간 (초)

캐시 무효화

  • 확실한 캐시 무효화를 할 수 있는 응답

    • Cache-Control: no-cache, no-store, must-revalidate
    • 이건 절대 캐시되면 안돼!!!! 라고 하면 이 세가지를 다 넣어줘야 함
    • 안 넣어주면 클라이언트가 휴리스틱하게 대충 보고 아무거나 캐시에 막 집어넣을 수 있음
  • Pragma: no-cache

    • HTTP 1.0 하위 호환
    • 과거 브라우저에서 요청이 있을 수도 있으니까 이것까지 넣어 주면 거의 확실하게 막을 수 있음
  • Cache-Control: must-revalidate

    • 캐시 만료 후 최초 조회 시 원 서버에 검증해야 함
    • 원 서버 접근 실패 시 반드시 오류가 발생해야 함 (504 Gateway Timeout)
    • 캐시 유효시간 내라면 캐시를 사용함 (만료되기 전까지는 그냥 써도 ㄱㅊ)
  • no-cache vs must-reavalidate

    • no-cache 응답을 받으면 다시 요청을 보낼 때도 no-cache를 포함시킬 수 있다. (아마 그래야만 하겠죠?)
    • 프록시 캐시 서버가 이 요청을 보고 원서버에 검증 요청을 보낸다. (no-cache, ETag 포함)
    • 원서버에서 검증하고 응답을 보낸다.
  • 만약 프록시 캐시와 원서버의 연결이 끊겼을 때:

    • 프록시 캐시 서버가 갱신되지 않은 캐시라도 일단 보여주는 것 vs 에러 발생
    • no-cache의 경우 둘 중 적절히 선택해서 보여줄 수 있다.
    • must-revalidate의 경우, 무조건 504 Gateway Timeout이 발생해야 한다. (계좌 이체, 상품 구매처럼 돈과 관련된 아주 중요한 요청이라고 생각해 보장)

0개의 댓글