캐시가 필요한 이유
- 데이터가 변경되지 않아도 동일한 데이터를 계속 네트워크를 통해 다운로드 받아야한다.
- 인터넷 네트워크는 느리다 => 결국 느린 사용자 경험
캐시 작동 방식
- 최초 요청으로 서버로부터 받은 응답 결과를 웹브라우저는 브라우저 캐시에 저장
- 이 응답 메시지에는 캐시 유효 시간(cache-control: max-age=60)이 있다.
- 이후 동일한 요청이 있을 때 캐시 유효 시간을 검증하여 유효하다면 캐시에서 이를 조회하면 된다.
- 만약 유효 시간이 초과했다면? => 서버에 다시 요청하여 데이터를 받으면 된다.
근데 유효 시간이 초과해서 서버에 다시 요청을 했을 때,
서버의 데이터가 변경되었다면 당연히 데이터를 새로 받아야하겠지만,
변하지 않았을 경우 브라우저 캐시에 있는 캐시를 재사용하는 것이 훨씬 효율적일 것이다.
여기서 클라이언트의 데이터와 서버의 데이터가 같다는 사실을 검증할 수 있는 방법이 필요하다.
검증 헤더
- 서버가 응답메시지에 검증헤더를 추가(Last-Modified: 마지막 변경 시간)
- 클라이언트는 이 결과까지 캐시에 저장
- 이후 캐시 시간이 초과되어 서버에 재요청을 보낼 때, 헤더에 if-modified-since를 추가하여 요청을 보냄
- 여기엔 전에 받았던 마지막 변경 시간이 들어있고, 서버가 이를 확인
- 만약 같다면, 캐시를 써도 되는 상황 => 304 Not Modified를 보냄(3xx는 리다이렉트, 캐시로 리다이렉트됨)
- 이 응답에는 HTTP body가 없기 때문에 용량을 많이 절약할 수 있다.
* if-modified-since 이후 데이터가 수정되었다면 서버는 200 OK 응답메시지를 보내고 웹브라우저는 새 데이터와 함께 캐시 정보를 갱신한다.
Last-Modified, If-Modified-Since의 단점
- 1초 미만 단위로 캐시 조정이 불가
- 데이터가 a에서 b로 수정되고 다시 a로 수정되었다면 last-modified는 바뀔테지만 실제 데이터는 사실 바뀌지 않은 것임. 이때 이러한 사실을 판단할 방법이 없음.
- 서버에서 별도의 캐시 로직을 관리할 수 없음
또 다른 방법 - ETag(Entity Tag)
- 캐시용 데이터에 고유한 버전을 달아두고 만약 데이터가 변경되면 이를 변경함
- Hash를 통한 방법이기 때문에 a->b->a로 데이터가 바뀌어도 기존 a가 가지고 있던 버전을 다시 새 a가 갖게 됨
- 이제 데이터의 변경여부는 단순히 이 버전이 일치하는가를 판별하면 됨!
- if-Modified-Since 대신 if-None-Match를 사용, 데이터가 그대로면 No => 304 Not Modified 응답을 보냄
- 캐시 제어 로직을 서버에서 완전히 관리할 수 있다(ETag 갱신 로직 관리)
캐시 헤더
• Cache-Control: max-age : 캐시 유효 시간, 초 단위
• Cache-Control: no-cache : 데이터는 캐시해도 되지만, 항상 원(origin) 서버에 검증하고 사용
• Cache-Control: no-store : 데이터에 민감한 정보가 있으므로 저장하면 안됨(메모리에서 사용하고 최대한 빨리 삭제)
• (옛버전) Pragma: no-cache : HTTP 1.0 하위 호환
• (옛버전) expires: Mon, 01 Jan 1990 00:00:00 GMT : 캐시 만료일을 정확한 날짜로 지정
검증헤더에는 ETag: "v1.0", Last-Modified: Thu, 04 Jun 2020 07:19:24 GMT 등이 사용됨
조건부 요청 헤더는
• If-Match, If-None-Match: ETag 값 사용
• If-Modified-Since, If-Unmodified-Since: Last-Modified 값 사용
프록시 캐시
응답 속도를 줄이기 위해 원서버 이외에 프록시 캐시 서버를 주어 응답 속도를 향상시킨다.
클라이언트의 웹브라우저가 갖고있는 브라우저 캐시를 private 캐시,
프록시 캐시 서버가 갔고 있는 캐시를 public 캐시라고 구분짓는다.
다양한 캐시 지시어
• Cache-Control: public : 응답이 public 캐시에 저장되어도 됨
• Cache-Control: private : 응답이 해당 사용자만을 위한 것임, private 캐시에 저장해야 함(priavte이 default)
• Cache-Control: s-maxage : 프록시 캐시에만 적용되는 max-age
• Age: 60 (HTTP 헤더) : 오리진 서버에서 응답 후 프록시 캐시 내에 머문 시간(초)
캐시 무효화
캐시 설정이 자동적으로 되는 경우가 많은데 다음과 같은 조건을 달아주면 확실하게 캐시 무효화를 할 수 있다.
• Cache-Control: no-cache, no-store, must-revalidate
• Pragma: no-cache
• Cache-Control: no-cache
- 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용
• Cache-Control: no-store
- 데이터에 민감한 정보가 있으므로 저장하면 안됨(메모리에서 사용하고 최대한 빨리 삭제)
• Cache-Control: must-revalidate
- 캐시 만료후 최초 조회시 원 서버에 검증해야함
- 원 서버 접근 실패시 반드시 오류가 발생해야함 - 504(Gateway Timeout)
- must-revalidate는 캐시 유효 시간이라면 캐시를 사용함
• Pragma: no-cache
- HTTP 1.0 하위 호환
no-cache가 있는데 굳이 must-revalidate가 필요한 이유는??
프록시 캐시 서버와 원 서버 사이에 네트워크 단절이 발생했다고 가정해보자.
아래와 같은 이유로, must-revalidate가 있어야 좀 더 확실한 무효화가 가능해짐.
no-cache만 사용하게 되면 결제 이후 어떠한 문제로 인해 이전 잔고 금액이 반환될 수도 있음
-----------------------------------------
참고 : 인프런 김영한님 강의(모든 개발자를 위한 HTTP 웹 기본 지식)
댓글