나의 브을로오그으

#9. [HTTP] 캐시와 조건부 요청 본문

HTTP

#9. [HTTP] 캐시와 조건부 요청

__jhp_+ 2022. 8. 8. 08:47

캐시 기본 동작

 

캐시가 없을 때

첫 번째 요청

 

요청

GET /star.jpg HTTP/1.1

 

응답

HTTP/1.1 200 OK
Content-Type: image/jpeg
Content-Length: 34012

aksjdfikjbkjbnkjnzxcokjjifvnike15423kcv
sodnvndkaskfkdvbk125kcvk2kvfk4kbkdsfbk

약 1.1M 크기의 이미지를 보내고, 웹브라우저에서 해당 이미지를 랜더링

이후 캐시가 없을 때 또 한번 요청을 하게되면 아까전 요청과 똑같이 응답을 내려준다.

 

 

캐시가 없을 때

- 데이터가 변경되지 않아도 계속 네트워크를 통해서 데이터를 다운로드 받아야 한다.

- 인터넷 네트워크는 매우 느리고 비싸다.

- 브라우저 로딩 속도가 느리다.

- 느린 사용자 경험

 

 

캐시 적용

첫 번째 요청

GET /star.jpg HTTP/1.1
HTTP/1.1 200 OK
Content-Type: image/jpeg
cache-control: max-age=60
Content-Length: 34012

klakerngkdlngkerngknfg214kjdfbkn3kmfbv3
dfkgn2klnkldfblmlkm21kmlkfgldkflrthlrth

 

캐시 적용

- 캐시 덕분에 캐시 가능 시간동안 네트워크를 사용하지 않아도 된다.

- 비싼 네트워크 사용량을 줄일 수 있다.

- 브라우저 로딩 속도가 매우 빠르다.

- 빠른 사용자 경험

 

 

캐시 시간 초과

- 캐시 유효 시간이 초과하면, 서버를 통해 데이터를 다시 조회하고, 캐시를 갱신한다.

- 이때 다시 네트워크 다운로드가 발생한다.

 

여기서!!!

캐시가 만료가 되었더라도, 클라이언트 캐시에 저장된 이미지와 서버에 재요청한 이미지가 같은 이미지라면? 그럼에도 불구하고 전부 다운로드 받는게 효율적일까???

 

 

검증 헤더와 조건부 요청1

캐시 시간 초과

- 캐시 유효 시간이 초과해서 서버에 다시 요청하면 다음 두 가지 상황이 나타난다.

1. 서버에서 기존 데이터를 변경함 (이건 무조건 다시 서버에서 다운로드 받아서 캐시를 갱신, 웹 브라우저에서 랜더링)

2. 서버에서 기존 데이터를 변경하지 않음. (이런 경우>?)

 

- 캐시 만료후에도 서버에서 데이터를 변경하지 않음

- 생각해보면 데이터를 전송하는 대신에 저장해 두었던 캐시를 재사용 할 수 있다.

- 단 클라이언트의 데이터와 서버의 데이터가 같다는 사실을 확인할 수 있는 방법 필요 

 

 

검증 헤더 추가

첫 번째 요청

(Last-Modified 는 UTC 시간을 기준으로 해야한다.)

 

이렇게 캐시가 만료되었을 때 if-modified-since(데이터 최종 수정일)을 추가해서 재요청을 하게된다.

 

(서버에서는 이렇게 클라이언트에서 보낸 요청의 if-modified-since 필드의 값과 서버에 있는 리소스의 데이터 최종 수정일을 비교하여 일치하는지, 일치하지 않는지를 확인한다.

 

(이부분이 중요함. status code는 수정된것이 없다는 메시지와 함꼐 HTTP Body를 보내지 않음. (변경된것이 없기 때문에)

 

(이렇게 검증 헤더(Last-Modified)와 조건부 요청 (if-modified-since)를 같이 사용한다. 이것을 조합해서 브라우저의 로컬 캐시에 있는 데이터를 재사용하거나, 서버로부터의 불필요한 다운로드를 하지 않을 수 있다!!!)

 

검증 헤더와 조건부 요청

정리

- 캐시 유효 시간이 초과해도, 서버의 데이터가 갱신되지 않으면

- 304 Not Modified + 헤더 메타 정보만 응답(바디 X)

- 클라이언트는 서버가 보낸 응답 헤더 정보로 캐시의 메타 정보를 갱신

- 클라이언트는 캐시에 저장되어 있는 데이터 재활용

- 결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드

- 매우 실용적인 해결책

 

 

검증 헤더와 조건부 요청

- 검증 헤더

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

  * Last-Modified, ETag

- 조건부 요청 헤더

  * 검증 헤더로 조건에 따른 분기

  * If-Modified-Since: Last-Modified 사용

  * If-None-Match: ETag 사용

  * 조건이 만족하면 200 OK

  * 조건이 만족하지 않으면 304 Not Modified

 

검증 헤더와 조건부 요청

예시

- If-Modified-Since: 이후에 데이터가 수정되었으면?

  * 데이터 미변경 예시

    * 캐시: 2020년 11월 10일 10:00:00 vs 서버: 2020년 11월 10일 10:00:00

    * 304 Not Modified, 헤더 데이터만 전송(BODY 미포함)

(리다이렉션 스테이터스 코드, 의미는 너의 브라우저 캐시에 저장된 데이터를 불러와서 리다이렉션 해라)

- 데이터 변경 예시

  * 캐시: 2020년 11월 10일 10:00:00 vs 서버: 2020년 11월 10일 11:00:00

  * 200 OK, 모든 데이터 전송(BODY 포함)

  * 전송 용량 1.1M (헤더 0.1M, 바디 1.0M)

 

검증 헤더와 조건부 요청

Last-Modified, If-Modified-Since 단점

- 1초 미만(0.x초) 단위로 캐시 조정이 불가능

- 날짜 기반의 로직 사용

- 데이터를 수정해서 날짜가 다르지만, 같은 데이터를 수정해서 데이터 결과가 똑같은 경우

- 서버에서 별도의 캐시 로직을 관리하고 싶은 경우

  * 예) 스페이스나 주석처럼 크게 영향이 없는 변경에서 캐시를 유지하고 싶은 경우

 

검증 헤더와 조건부 요청

ETag, If-None-Match

- ETag(Entity Tag)

- 캐시용 데이터에 임의의 고유한 버전 이름을 달아둠

  * 예) ETag: "v1.0", ETag: "a2jiodwjekjl3"

- 데이터가 변경되면 이 이름을 바꾸어서 변경함(Hash를 다시 생성)

  * 예) ETag: "aaaaa" -> ETag: "bbbbb"

- 진짜 단순하게 ETag만 보내서 같으면 유지, 다르면 다시 받기!

 

검증 헤더와 조건부 요청

ETag, If-None-Match 정리

- 진짜 단순하게 ETag만 서버에 보내서 같으면 유지, 다르면 다시 받기!

- 캐시 제어 로직을 서버에서 완전히 관리

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

- 예)

  * 서버는 배타 오픈 기간인 3일 동안 파일이 변경되어도 ETag를 동일하게 유지

  * 애플리케이션 배포 주기에 맞추어 ETag 모두 갱신

 

 

캐시 조건부 요청 헤더

캐시 제어 헤더

- Cache-Control: 캐시 제어

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

- Expires: 캐시 유효 시간(하위 호환)

 

Cache-Control

캐시 지시어(directives)

(이게 가장 중요, pragma, expires는 하위호환으로 사용, 지금은 Cache-Control로 다 가능)

- Cache-Control: max-age

  * 캐시 유효 시간, 초 단위(보통 길게 잡음)

- Cache-Control: no-cache

  * 데이터는 캐시해도 되지만, 항상 원(origin) 서버에 검증하고 사용해야 한다는 의미

(중간에 캐시서버도 있음. 그러나 검증은 항상 실제 리소스가 있는 서버에서 검증해야 한다는 의미)

- Cache-Control: no-store

  * 데이터에 민감한 정보가 있으므로 저장하면 안된다는 의미

(메모리에서 사용하고 최대한 빨리 삭제)

 

Pragma

캐시 제어(하위 호환)

- Pragma: no-cache

- 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는 무시

 

 

검증 헤더와 조건부 요청 헤더

- 검증 헤더 (Validator)

  * ETag: "v1.0", ETag: "asid93jkrh2l"

  * Last-Modified: Thu, 04 Jun 2020 07:19:24 GMT

- 조건부 요청 헤더

  * If-Match, If-None-Match: ETag값 사용

  * If-Modified-Since, If-Unmodified-Since: Last-Modified 값 사용

 

 

프록시 캐시

 

원 서버 직접 접근

origin 서버

한국 클라이언트 -> 미국 서버

이미지 하나 다운받는데에도 시간이 오래 걸림.

 

이렇게 한국 어딘가에 프록시 캐시 서버를 두고, 미국 서버의 리소스 요청 시 DNS를 조작하여 프록시 캐시 서버로 요청이 가게 되고 이 프록시 캐시 서버에 저장된 리소스가 있는지 찾아보고, 없다면 이 프록시 캐시 서버에서 미국에 있는 원 서버에 요청하여 받아오면, 이 결과를 클라이언트에게 응답한다. 

(잘 생각해보면, 첫번째 유저는 첫 요청시 느릴거고, 두 번째부터는 프록시 캐시 서버에 저장되어 있기 때문에 빠를 것이다. 글로벌 서비스를 하는 회사들은 이렇게 국가별로 프록시 서버를 둔다.

예) 사람들이 많이 보는 유튜브 컨텐츠는 굉장히 빠르고, 사람들이 잘 않보는 유튜브 영상은 로딩 속도가 많이 느리다.)

 

 

 

 

Cache-Control

캐시 지시어(directives) - 기타

- Cache-Control: public

  * 응답이 public 캐시에 저장되어도 됨.

- Cache-Control: private

  * 응답이 해당 사용자만을 위한 것임, private 캐시에 저장해야 함(기본값)

(↓ 밑에는 이런게 있다 정도만!)

- Cache-Control: s-maxage

  * 프록시 캐시에만 적용되는 max-age

- Age: 60 (HTTP 헤더)

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

 

 

캐시 무효화

 

Cache-Control

확실한 캐시 무효화 응답

- Cache-Control: no-cache, no-store, must-revalidate

- pragma: no-cache

  * HTTP 1.0 하위 호환

예) 사용자 로그인 정보, 현재 고객의 통장 잔고 등?

위의 헤더를 넣어주면 어느정도 충분하고, 하위 호환도 고려해서 pragma까지 넣어주면 됨.

 

 

Cache-Control

캐시 지시어(directives) - 확실한 캐시 무효화

- Cache-Control: no-cache

  * 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용(이름에 주의!)

- Cache-Control: no-store

  * 데이터에 민감한 정보가 있으므로 저장하면 안됨

(메모리에서 사용하고 최대한 빨리 삭제)

- Cache-Control: must-revalidate

  * 캐시 만료 후 최초 조회시 원 서버에 검증해야함.

  * 원 서버 접근 실패시 반드시 오류가 발생해야함 - 504(Gateway Timeout)

  * must-revalidate는 캐시 유효 시간이라면 캐시를 사용함

- Pragma: no-cache

  * HTTP 1.0 하위 호환

(네이버는 이렇게 구현되어 있고, 구글은 글로벌 서비스이므로 하위호환을 위해 더많은 설정이 되어있음)

 

 

이렇게 들어오면, 처음 캐시 서버에 요청했을 때 Cache-Control의 no-cache 설정이 되어있으므로 캐시 서버에서 처리하지 않고 원서버로 처리해야 함. 따라서 프록시 캐시 서버에서 원 서버로 처리 요청함.

(이렇게 오류가 난다면에 대한 대처법, 셋팅 이렇게 할 수도 있다는 것을 보여준 예시임)

 

예) 통장 잔고의 경우 저런 네트워크 오류로 처리가 되지 않았다면 이부분에 대해서는 확실하게 오류가 나서 과거의 데이터가 보이면 안된다.(프록시 캐시 서버에서 과거 정보가 보인다든가 이런 문제들)

그래서 이런것 때문에 확실하게 하려면 must-revalidate까지 넣어줘야 한다.

 

정리

Cache-Control: no-cache, no-store, must-revalidate

Pragma: no-cache (HTTP/1.1 하위호환)

'HTTP' 카테고리의 다른 글

#8. [HTTP] HTTP 일반 헤더  (0) 2022.08.07
#7. [HTTP] HTTP 상태코드  (0) 2022.08.03
#6. [HTTP] HTTP API 설계 예시  (0) 2022.08.02
#5. [HTTP] HTTP 메서드 활용  (0) 2022.08.02
#4. [HTTP] 클라이언트 서버 구조와 HTTP 특징  (0) 2022.08.01