캐시 기본 동작 💡
캐시 미적용
- 데이터가 변경되지 않아도 계속 네트워크를 통해 데이터를 다운로드 받아야 한다.
- 인터넷 네트워크는 매우 느리고 비싸다.
- 브라우저 로딩 속도가 느리다.
- 느린 사용자 경험
캐시 적용
위 요청에 대한 응답으로 cache-control: max-age=60은 캐시 저장소에 60초간만 유효하도록 저장하겠다는 의미
- 캐시 덕분에 캐시 가능 시간동안 네트워크를 사용하지 않아도 된다. (60초 동안은 캐시 저장소에서 가져오면 됨)
- 비싼 네트워크 사용량을 줄일 수 있다.
- 브라우저 로딩 속도가 매우 빠르다.
- 빠른 사용자 경험
캐시 적용 - 캐시 시간 초과
cache-control: max-age=60 으로 캐시 저장소에 데이터를 60초간만 유효하도록 저장해놓았다.
그런데 만약 60초가 경과되고 나서 브라우저가 다시 서버측에 요청을 하게 된다면 ?
서버를 통해 데이터를 다시 조회하고, 캐시를 갱신한다.
이때 다시 네트워크 다운로드가 발생하기 때문에 이를 해결할 방안이 필요하다.
검증 헤더와 조건부 요청 1 💡
캐시 시간 초과
캐시의 유효 시간이 초과해서 서버에 다시 요청을 하게 되면 다음 두 가지의 상황이 나타날 수 있다.
1) 서버에서 기존 데이터를 변경하지 않음 (노란색 별 그대로)
2) 서버에서 기존 데이터를 변경함 (노란색 별 → 초록색 별)
예시 살펴보기
- 캐시 만료후에도 서버에서 데이터를 변경하지 않은 상태이다.
- 생각해보면 데이터를 전송하는 대신 저장해 두었던 캐시를 재사용 할 수 있다.
- 단, 클라이언트의 데이터와 서버의 데이터가 같다는 사실을 확인할 수 있는 방법이 필요하다.
검증 헤더 추가
위 그림의 헤더에 Last-Modified 는 데이터가 마지막에 수정된 시간을 의미한다.
따라서 해당 정보를 응답으로 반환해주면 해당 정보를 저장해놓았다가 재요청시 헤더를 추가해 요청하면
클라이언트와 서버측의 데이터가 동일한지 판별할 수 있게 되고, 이를 통해서 데이터의 수정이 일어나지 않았다면
캐시가 유효하지 않더라도 재사용을 할 수 있게 된다.
→ 재요청을 해보자.
요청시 if-modified-since 에 응답으로 부터 받았던 데이터 수정시간을 붙여 요청하게 되면 , 서버와 클라이언트 측을 비교하게 되고 만약 수정되지 않았다면 ?
if-modified-since (이후에 수정된적이 있느냐?) 라는 질문에 false 가 되기 때문에
304 Not Modified를 반환해주게 된다. 또 캐시이기 때문에 HTTP Body 가 없는 것을 볼 수 있다. (지난 글에 설명)
정리
정리해보면, 클라이언트 측에서 요청을 보내는 시간 + 응답을 받아 데이터를 다운로드 하는 시간이 있을 때
데이터의 수정이 없어 재사용을 하게 된다면 ? 요청을 보내서 클라이언트와 서버측을 비교하고 결국 동일하면
요청을 보내는 시간만 생각하면 되기 때문에 데이터를 다운로드 하는 시간을 단축할 수 있다.
- 캐시 유효 시간이 초과해도, 서버의 데이터가 갱신되지 않는다면 ?
- 304 Not Modified + 헤더 메타 정보만 응답 (바디 X)
- 클라이언트는 서버가 보낸 응답 헤더 정보로 캐시의 메타 정보를 갱신
- 클라이언트는 캐시에 저장되어 있던 데이터 재활용
- 결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만 다운로드한다.
검증 헤더와 조건부 요청 2 💡
1) 검증 헤더
- 캐시 데이터와 서버 데이터가 같은지 검증하는 데이터
- Last-Modified (위에서 살펴봄) , ETag
2) 조건부 요청 헤더
- 검증 헤더로 조건에 따른 분기
- 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 미포함)
- 전송 용량 0.1M (헤더 0.1M , 바디 1.0M) - 해당 용량은 임의로 설정한 것임
- 데이터 변경 예시
- 캐시 : 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 초) 단위로 캐시 조정 불가
- 날짜 기반의 로직 사용
- 데이터를 수정해 날짜가 다르지만, 같은 데이터를 수정해서 데이터 결과가 똑같은 경우 (똑같음에도 다시 다운함)
- 즉, 같은 내용인데 새로 복붙해서 수정하는 방식을 통해 수정날짜만 다른 경우
- 서버에서 별도의 캐시 로직을 관리하고 싶은 경우
- ex) 스페이스, 주석처럼 크게 영향 없는 변경에서 캐시를 유지하고 싶을 때
ETag, If-None-Match
- ETag(Entity Tag)
- 캐시용 데이터에 임의의 고유한 버전 이름을 달아둠
- ex) ETag: "v1.0", ETag: "a2jiodwjekjl3"
- 데이터가 변경되면 이 이름을 바꾸어 변경함 (Hash를 다시 생성)
- ex) ETag: "aaaaa" → ETag: "bbbbb"
- 진짜 단순하게 ETag 만 보내서 같은지를 판별하여 같으면 캐시 유지, 다르면 다시 다운로드 하는 방식
첫 번째 요청)
ETag : "aaaaaaaaaa" 를 응답 결과로 보내면 해당 결과를 캐시에 저장한다. 마찬가지로 60초로 유효시간을 설정했기 때문에 캐시 저장소에서 60초간 유효하다.
시간이 초과되고 , 요청을 보내보자.
두 번째 요청)
If-None-Match : "aaaaaaaaaa" 라는 조건부 요청 헤더를 붙여서 요청을 보내면 캐시에 저장되어 있던 ETag와 서버측 ETag를 비교하고 데이터가 수정되지 않음을 확인할 수 있다.
따라서 304 Not Found 를 응답으로 보내주면서 클라이언트는 캐시를 재사용함으로써 용량을 단축시킬 수 있다.
ETag, If-None-Match 정리
- 단순하게 ETag만 서버에 보내서 같으면 유지, 다르면 다시 받기
- 캐시 제어 로직을 서버에서 완전히 관리한다.
- 클라이언트는 단순히 이 값을 서버에 제공 (클라이언트는 캐시 메커니즘을 전혀 모름)
- ex)
- 서버는 배타 오픈 기간인 3일 동안 파일이 변경 되어도 ETag를 동일하게 유지
- 애플리케이션 배포 주기에 맞추어 ETag 모두 갱신
캐시 제어 헤더
- Cache-Control : 캐시 제어
- Pragma : 캐시 제어 (하위 호환)
- Expires : 캐시 유효 기간 (하위 호환)
Cache-Control
캐시 지시어(directives)
1) Cache-Control : max-age
- 캐시 유효 시간, 초 단위
2) Cache-Control : no-cache
- 데이터는 캐시해도 되지만, 항상 원(origin) 서버에 검증하고 사용
3) Cache-Control : no-store
- 데이터에 민감한 정보가 있으므로 저장하면 안됨 (메모리에서 사용하고 최대한 빠르게 삭제)
여기서 2) Cache-Control:no-cache에 대한 설명을 조금 해보자면
항상 원(origin) 서버에 검증하고 사용해야 한다고 되어 있다. 여기서 원(origin)서버는 클라이언트측에서 요청을
보내면 결국 최종적으로 요청을 받는 서버측을 의미한다 (중간에 있는 서버 노드들 XXXX)
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: "asid93jkds123"
- Last-Modified : Thu, 04 Jun 2020 07:19:24 GMT
- 조건부 요청 헤더
- If-Match, If-None-Match: ETag 값 사용
- If-Modified-Since, If-Unmodified-Since: Last-Modified 값 사용
프록시 캐시
프록시는 클라이언트와 서버 사이에 대리로 통신을 수행하는 것을 가리켜 프록시(Proxy)라고 하며, 그 중계 기능을 하는 서버를 프록시 서버라고 한다.
예를 들어 한국에 있는 클라이언트가 미국에 있는 원 서버에 있는 특정 데이터를 가져와야 하는 상황이라고 가정해보자. 정보를 가져오기 위해선 거리가 있는 만큼 시간이 소요될 수 있다. 이 때 미국의 원 서버와 한국 클라이언트 중간에 위치한 한국의 캐시 서버를 위치시켜 시간을 줄일 수 있다.
이러한 클라이언트에서 사용하고 저장하는 캐시를 Private 캐시라고 하며, 프록시 캐시를 Public 이라고 한다.
프록시 관련 헤더 - Cache-Control
- 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 하위 호환
캐시를 확실하게 무효화 해야하는 경우라면 위와 같은 지시어를 적용해야 한다.
예를 들어, 통장 잔고와 같은 경우는 계속 갱신이 되야하기 때문에 캐시를 사용하지 않는 것이 좋다.
- 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의 차이점
no-cache는 캐시 서버에 요청을 하면 프록시 캐시 서버에 도착후에 원 서버에 요청을 한다.
만약, 프록시 캐시 서버와 원 서버 사이 네트워크 단절이 일어나 접근이 불가하면 ? no-cache에서 오류를 반환해주는 것이 아닌, 오래된 데이터라도 보여주자라는 의미로 200 OK를 응답으로 내보낸다.
must-revalidate는 위와 같은 상황에서 접근이 불가능 하다면 504(Gateway Timeout)이라는 오류를 내보낸다.
중요한 정보를 사용할 경우 예전 정보를 보여주면 큰 문제가 생길 수 있기 때문에 must-revalidate를 적용하자.
1) no-cache
2) must-revalidate
참고 자료
https://www.inflearn.com/course/http-%EC%9B%B9-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC/dashboard