이 글은 ASAC 06기를 수강하며 강의 자료 참고 및 추가 자료 수집을 통해 작성된 글입니다.
HTTP Cache 동작 : 검증을 통한 준실시간성 보장
캐시는 임시(반영구적) 저장을 위한 전략이자 실시간성을 아예 포기하는 것이 아닌 준실시간성을 보장하는 정책이다.
특정 주기에 따라 재검증하여 어느 시점까지는 실시간성을 보장한다.
그렇기 때문에 실시간성이 매우 중요한 데이터는 성능에 문제가 되더라도 캐시는 사용하지 않는 것이 맞다.
준실시간성 보장을 위해서 재검증을 한다고 하였는데, 재검증의 목적은 다음과 같다.
재검증의 목적
- 만료된 데이터의 삭제
- 준실시간성 보장
조건부 요청
재검증 방법은 조건부 요청을 사용하는 것이다.
=> 재검증 기준에 되는 값을 서버로 보낸다.
조건부 요청은 데이터를 보기 위한 요청이 아닌 HTTP Cache 데이터의 유효성 확인을 위한 요청이다.
조건부 요청 뒤에 항상 원요청을 보내는 것은 아니다.
조건부 요청은 CORS 요청 가능 여부 확인을 위한 Pre-flight 요청과 비슷하게 동작한다.
서버는 조건부 요청을 받고, 재검증 주기가 지나면 캐싱된 데이터를 재검증 기준에 따라서 재검증 한다.
재검증 주기
- 캐싱된 데이터를 써도 되는지 서버에게 물어보는 주기
- Max-Age 헤더로 설정한다. 자주 바뀌는 페이지는 짧게, 자주 바뀌지 않는 페이지는 길게
- 주기가 너무 길어도 안되고, 너무 짧아도 안된다.
- 비즈니스 관점에 따라서 설정한다.
재검증 기준
데이터를 사용해도 되는지에 대한 기준이다.
Last-Modified (수정일) | ETag (고유값) |
낮은 정확도 - 파일이 수정되지 않았는데 수정일이 변경되는 경우 - 텍스트 파일 수정 후, 다시 원상복구한 경우 수정일만 변경 |
높은 정확도 - 그렇기에 사실 Last-Modified 보다 ETag 사용이 좋다. - 다만, HTTP 1.0 혹은 1.1 호환성을 위해 둘 다 사용하는편 |
검사 방법 - If-Modified-Since : 바뀌었어? - If-Unmodified-Since : 안바뀌었어? |
검사 방법 - If-None-Match : 바뀌었어? - If-Match : 안바뀌었어? |
수정일 : Last-Modified 헤더
-> 데이터가 바뀌지 않은 경우 304 Not Modified 코드 반환
- 낮은 정확도를 가진다. 실제 데이터가 바뀌지 않았음에도 수정일은 바뀔 수 있기 때문이다.
Last-Modfied와 관련하여 조건부 요청에서 사용되는 헤더는 두가지가 존재한다.
- If-Modified-Since (GET, HEAD => 서버 상태 읽기 Method에서 사용)
- Yes => 바뀜
- 200 Resource Cache + 새 응답 캐시 (1개 요청. 조건부 요청에 대해서 바로 응답을 준다. 원요청을 다시 하지 않는다.)
- No => 안바뀜
- 304 Not Changed (GET, HEAD) => 캐싱된 자료가 변하지 않았음
- Yes => 바뀜
- If-Unmodified-Since (POST, DELETE, PUT, PATCH => 서버 상태 변경 Method에서 사용)
- Yes => 안바뀜
- 304 Not Changed
- No => 바뀜
- 412 Precondition Failed (조건부 요청에 대한 False 부정 응답) => 거부된다.
- Yes => 안바뀜
고유값 : ETag 헤더
-> Entity Tag, 일반적으로 Hash-Based. 캐시가 유효한지 여부를 고유값을 기반으로 판단한다.
- 일반적으로 해시를 기반으로 하기 때문에, 데이터에서 아주 작은 부분이라도 바뀌면 ETag는 달라진다.
ETag와 관련하여 조건부 요청에서 사용되는 헤더는 두가지가 존재한다.
- If-None-Match
- Yes => 바뀜
- 200 Resource Cache + 새 응답 캐시 (1개 요청. 조건부 요청에 대해서 바로 응답을 준다. 원요청을 다시 하지 않는다.)
- No => 안바뀜
- 304 Not Changed (GET, HEAD)
- +) 412 Precondition Failed. (POST, DELETE, PUT, PATCH => 서버 상태 변경 Method)
- Yes => 바뀜
- If-Match (POST, DELETE, PUT, PATCH => 서버 상태 변경)
- Yes => 안바뀜
- 304 Not Changed
- No => 바뀜
- 412 Precondition Failed (조건부 요청에 대한 False 부정 응답)
- Yes => 안바뀜
Hash란?
=> 객체의 대명사. 객체의 고유성을 대표하는 것
객체 비교를 예로 드는 경우, 객체의 모든 필드를 비교하는 대신 단 1개 필드 hash의 비교만으로 동등성을 검증할 수 있다.
- A 객체 : User { name: ‘Aaron’, age: 10, favorites: 'A', **... 총 100개의 필드들** }
- B 객체 : User { name: ‘Aaron’, age: 10, favorites: 'B', **... 총 100개의 필드들** }
- 100개의 필드들을 모두 비교해서 A 객체와 B 객체가 동등한지 검증하는 대신, hash 필드를 두어서 hash 필드의 비교로 동등성을 검증한다.
- @EqualsAndHashCode
- => 필드를 사용해 자동으로 해시 생성 및 비교를 가능하게 함
혹은 Github에서 각각의 커밋들은 고유한 hash 값을 가지고 있기 때문에, 버전 관리가 가능하다.
해시가 보안에 사용되는 경우
- 파일 다운로드 (파일 변조 여부 확인)
- 파일 업로드 시 파일과 파일로부터 계산된 해시값을 같이 업로드.
- 다운로드 받는 사람은 미리 공유된 키값을 사용해 파일로부터 해시를 생성한다. 생성된 해시와, 받은 해시를 비교해서 위/변조를 검증
- 표준 알고리즘은 HMAC(Hash-based Message Authentication Code)
- 비밀키 값은 미리 공유가 되어 있어야 한다.
- Open API (외부 서비스의 open api 응답이 변조됐는지 확인)
Open API도 파일 다운로드와 비슷하다. Open API가 파일의 역할
Cache-Control 헤더 설정
캐시 저장 여부
- no-store : 캐시 안 함
- no-cache : 캐시 함. 그러나 매번 재검증 후 사용 (패킷 경량화)
캐시 저장 장소
- public 설정
- 일반적으로 프록시에도 저장, 웹 브라우저에도 저장
- private 설정
- 웹 브라우저에만 저장
캐시 재검증 주기
- max-age : 특정한 기간.(ex. 3600초) 입력하면 Expires(유효시간. 특정한 시점)으로 변환.
- max-age는 revalidate의 최소조건이다. => max-age가 지나고, 요청을 보낸 시점에 revalidate
- max-age=0 => no-cache와 같은 의미이다. 매번 재검증
- s-maxage : 프록시 캐시에만 적용되는 유효기간
- s-maxage=31536000(1년), max-age=0의 의미
- => 웹 브라우저는 웹 서버가 아닌 CDN에 재검증을 수행한다. CDN은 1년 주기로 웹 서버에 재검증을 한다.
- => 웹 브라우저는 CDN이 자체적으로 Invalidation(무효화) 하지 않는 한 1년 동안 같은 데이터만 가진다.
재검증 강제
- must-revalidate : 꼭 "웹 서버"와 직접 재검증이 완료된 뒤 캐시를 사용해야한다는 의미의 정책
- 재검증 필수. max-age와 상관없이 must-revalidate가 활성화 되어있다면 무조건 매번 물어본다.
- 서버와의 접속 문제로 재검증이 실패한 경우 기본 행동은 그냥 기존 캐시되어있는 데이터를 반환하는 것이지만,
- must-revalidate가 활성화 되어있다면, 504 에러 발생. => 웹 서버와 연락이 안됨!
must-revalidate와 max-age=0의 차이점
- max-age=0 : 매번 재검증
서버가 죽으면 max-age는 재검증이 불가능. 그냥 캐시 값을 응답한다. - must-revalidate : 매번 재검증 + "재검증된 응답만 받을 수 있다."
must-revalidate는 서버가 죽으면 재검증이 불가능. 응답 하지 않는다. => 504 에러(서버 에러) 반환
SWR (Stale-While-Revalidate)
위 그림은 캐시 조회와 재검증을 시각화한 것이다.
첫번째는 max-age + SWR(Stale-While-Revalidate) 을 시각화한 것이고,
두번째는 max-age만을 사용했을 때를 시각화한 것이다.
max-age는 캐시 조회와 재검증이 겹쳐있지 않다.
재검증이 필요한 경우에는 새로운 데이터를 서버에서 가져오기 위해서 Read Time이 생긴다.
그러나 SWR은 캐시 조회와 재검증을 동시에 진행한다. 즉 겹쳐있다.
SWR의 동작 원리는 다음과 같다.
- 재검증 요청과 동시에 현재 응답은 캐시의 것으로 응답.
- => 현재에 캐싱된 컨텐츠를 즉시 로드하는 즉시성
- 이름 그대로 재검증을 하는 중에는 Stale한 컨텐츠를 즉시 반환한다.
- 이후에 재검증 응답이 오면 캐시에 넣어놓고 다음 요청부터 사용된다.
- => 미래에 업데이트된 캐싱 컨텐츠가 사용될 수 있도록 보장하는 최신성
SWR은 속도가 매우 빠르다. 그렇기 때문에 사용자는 Read Time이 없는 것처럼 느끼게 된다.
SWR은 재검증을 통해서 최신성을 유지할 수 있지만, 응답을 받기 전까지는 최신성을 보장할 수 없다.
SWR 역시 주기적으로 재검증을 하는 것은 아니다. 요청이 올 때마다 재검증을 한다.
최악의 Cache-Control
Cache-Control: no-store, no-cache, max-age=0, must-revalidate, proxy-revalidate
모든 헤더 값 무지성으로 때려박지 말 것. 이는 구식 프록시 캐시이다.
'ASAC' 카테고리의 다른 글
[ASAC 06] 웹 저장소 (쿠키, 세션, 웹 스토리지) (4) | 2024.09.11 |
---|---|
[ASAC 06] 프록시 사용 목적, 기능과 예시 (1) | 2024.09.01 |
[ASAC 06] 캐시 1편 - 사용 목적과 위치에 따른 분류 (1) | 2024.09.01 |
[ASAC 06] 수평적 확장에 의한 다중 서버에서의 배포 전략 (1) | 2024.08.28 |
[ASAC 06] 컴파일러 언어와 인터프리터 언어 (0) | 2024.08.28 |