티스토리 뷰
인프런 김영한 님의 모든 개발자를 위한 HTTP 웹 기본 지식 강의를 듣고 내용을 정리한 글입니다.
1. 캐시 기본 동작
캐시가 없을 때
첫 번째 요청
GET /star.jpg -> HTTP/1.1 200 OK
이때 응답이 1.1mb라고 해보자.
두 번째 요청
GET /star.jpg -> HTTP/1.1 200 OK
똑같이 응답이 1.1mb
데이터가 변경되지 않아도 계속 네트워크를 통해서 데이터를 다운로드 받아야 한다.
인터넷 네트워크는 PC 메모리나 하드디스크에 비해 매우 느리고 비싸다.
브라우저 로딩속도가 느리다 -> 느린 사용자 경험
캐시 적용
첫 번째 요청
GET /star.jpg -> HTTP/1.1 200 OK
이때 응답 헤더 필드에
cache-control: max-age=60 (캐시가 유효한 시간 - 초)
1.1mb 응답. 이 응답 결과를 브라우저 캐시 저장소에 저장(60초 동안)
두 번째 요청 시 우선 캐시를 탐색 -> 캐시 유효시간 내라면 네트워크 이용하지 않고 캐시를 이용
캐시 덕분에 캐시 가능 시간 동안 네트워크를 사용하지 않아도 되고 비싼 네트워크 사용량을 줄일 수 있다.
브라우저 로딩 속도가 매우 빠르다. -> 빠른 사용자 경험
세 번째 요청 - 캐시 시간 초과
캐시를 탐색 -> 유효시간 끝나 -> 네트워크 이용해서 다시 요청 -> 다시 응답 받아서 기존 거 지우고 새로 저장
캐시 유효시간이 초과하면 서버를 통해 데이터를 다시 조회하고 캐시를 갱신한다.
이때 다시 네트워크 다운로드가 발생
2. 검증 헤더와 조건부 요청1
캐시 시간 초과
캐시 유효 시간이 초과해서 서버에 다시 요청하면 다음 두 가지 상황이 있을 수 있다.
- 서버에서 기존 데이터를 변경함
- 서버에서 기존 데이터를 변경하지 않음
2번의 경우에는 네트워크를 쓰는 게 낭비.
캐시 만료 후에도 서버에서 데이터를 변경하지 않았을 때
데이터를 전송하는 대신에 저장해둔 캐시를 재사용할 수 있다.
단 클라이언트의 데이터와 서버의 데이터가 같다는 사실을 확인할 수 있는 방법이 필요하다.
그때 사용 하는 게 검증헤더다.
첫 번째 요청 때 응답 헤더에
Last-Modified: 2020년 11월 10일 10:00:00 (데이터가 마지막에 수정된 시간. UTC 형식임 원래)
위와 같이 추가하고
응답 결과를 캐시에 저장한다.
두 번째 요청 때 캐시시간 초과했다면
요청을 다시 보낼 때 last-modifed가 있으면
요청 헤더에
if-modified-since: UTC 시간
서버가 이걸 확인한 다음. 데이터 수정시간과 if-modified-since
를 비교해서 데이터가 변하지 않았다고 판단한 뒤
304 Not Modified 라는 상태코드를 응답한다.
이때, cache-contral, last-modified그대로 가는데
메세지 바디가 없음.
헤더가 0.1m, 바디가 1.0m이라고 하면 1m를 아낄 수 있음
클라이언트는 이 응답을 확인해서 캐시 저장소에 있는 데이터의 cache-control 갱신 -> 그 다음부터 다시 캐시 사용
Last-modified
는 검증헤더고, if-modified-since
는 조건부 요청이다.
정리
캐시 유효 시간이 초과해도 서버의 데이터가 갱신되지 않으면
304 Not Modified + 헤더 메타 정보만 응답(바디 안 줘)
클라는 서버가 보낸 응답 헤더 정보로 캐시의 메타 정보를 갱신
클라는 캐시에 저장되어 있는 데이터 재활용
결과적으로 네트워크 다운로드가 발생은 하지만 용량이 적은 헤더 정보만 다운로드
매우 실용적인 해결책
3. 검증 헤더와 조건부 요청2
검증헤더
- 캐시 데이터와 서버 데이터가 같은지 검증하는 데이터
- 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, 헤더 데이터만 전송(바디 미포함) -> 클라의 캐시 저장소로 리다이렉션
- 전송용량이 많이 줄어
- 데이터 변경 예시
- 캐시 2020년 11월 10일 10:00:00 vs 서버 캐시 2020년 11월 10일 11:00:00
- 200 OK, 모든 데이터 전송(바디 포함)
Last-modified, if-modified-since 단점
- 1초 미만(0.x초)단위로 캐시 조정 불가능
- 날짜 기반의 로직 사용
- 데이터를 수정해서 날짜가 다르지만, 같은 데이터를 수정해서 데이터 결과가 똑같은 경우(날짜는 갱신되었지만 컨텐츠 자체는 변경이 안됨)를 거를 수 없음
- 서버에서 별도의 캐시 로직을 관리하고 싶은 경우
- 스페이스나 주석 처럼 크게 영향이 없는 변경에서 캐시를 유지하고 싶은 경우
Etag, If-None-Match
- 서버에서 완전히 캐시 매커니즘을 콘트롤 할 수 있는 방법
Etag(Entitiy Tag)
캐시용 데이터에 임의의 고유한 버전 이름을 달아둠
- ETag: "v1.1" Etag: "adfadfadf"
데이터가 변경되면 이 이름을 바꾸어서 변경함)Hash를 다시 생성
- ETag: "aaaa" -> ETag: "bbbb"
진짜 단순하게 ETag만 보내서 같으면 유지, 다르면 다시 받음
예시
ETag 첫번째요청
요청 -> 응답 헤더에 cache-control과 Etag: "aaaa" 내려줌
클라는 응답결과를 캐시에 저장
캐시 유효시간이 지나면 클라는 If-None-Match: "aaaa" 헤더 추가 후 요청
서버는 이걸 확인해서 똑같으면 304, 다르면 200
304일 때는 헤더만 응답, 200이면 바디까지
정리
진짜 단순하게 ETag만 보내서 같으면 유지 다르면 다시 받기
캐시 제어 로직을 서버에서 완전히 관리
클라는 캐시 로직을 전혀 모름
클라이언트는 단순히 이값을 서버에 제공
서버에서 별도의 로직을 통해 캐시를 관리할 수 있다.
예) 서버가 애플리케이션 배포 주기에 맞춰 ETag 모두 갱신. 이런 거 가능
4. 캐시와 조건부 요청 헤더
캐시 제어 헤더
Cache-Control
- 캐시 제어 제일 중요
Cache-Control: max-age(시간)
- 캐시 유효시간, 초 단위
- 이걸로 다할 수 잇음
Cache-Control: no-cache
- 데이터는 캐시해도 되지만, 항상 원 서버(origin)에 검증하고 사용(서버에 데이터가 바뀌었는지 아닌지 항상 검증하고 써라)
- 클라와 서버 사이에는 캐시 프록시 서버라는 게 존재. 프록시 서버 말고 내가 응답을 받으려고 하는 서버에 검증
Cache-Contral: no-store
- 데이터에 민감한 정보가 있으므로 저장하면 안됨
- 보통 캐시를 사용하면 하드디스크에 저장되는데, 메모리에서만 사용하고 최대한 빨리 삭제
- 노캐시랑 노스토어랑 헷갈리지 말자
Pragma
- 캐시 제어(하위호환)
- Pragma: no-cache
- HTTP/1.0 하위호환(호환성을 위해서만 사용)
Expires
- 캐시 제어(하위호환)
- Expires: 날짜 지정
- 캐시 만료일을 날짜로 지정
- 1.0부터 사용
- 지금은 캐시컨트롤 맥스 에이지 사용 권장
- 캐시컨트롤이랑 같이 사용 시 익스파이어스 무시
검증헤더(Validator)
- ETag: "v1.0", ETag: 'ASDF"
- Last-Modified: Thu, 04, Jun 2020 07:19:24 GMT
조건부 요청 헤더
- If-Match, If-None-Match: Etag 값 사용
- If-Modified-Since, If-Unmodified-Since: Last-Modified 사용
5. 프록시 캐시
원 서버(origin server) 직접 접근
한국에 있는 클라이언트 -> 미국에 있는 원 서버(실제 리소스가 있는 서버)
이렇게 클라이언트와 서버의 물리적 거리가 멀면 요청과 응답을 주고 받는 데 시간이 오래 걸리게 된다.
한국 어딘가에 프록시 캐시 서버를 두고. 클라들이 이 프록시 서버에 접근하게 만든다.
예) 유튜브도 한국 사람들이 많이 보는 영상은 로딩이 빠르고, 많이 안 보는 영상은 느린데, 많이 보는 영상은 프록시 서버에 저장되어 있기 때문이다.
클라는 내 로컬에 저장되는 private 캐시, 프록시 서버는 public 캐시
프록시 서버의 운영 방식은 여러가지가 있겠지만 대표적으로
1. 클라1이 어떤 리소스를 처음으로 요청하면,
2. 프록시 서버에는 해당 리소스에 대한 캐시가 없으므로 원서버를 통해 리소스를 가져온 뒤
3. 프록시 캐시 서버에 해당 리소스를 저장 후 클라1에게 응답한다.
4. 그 다음부터 요청하는 클라2, 클라3는 동일한 리소스에 대해 프록시 서버에서 응답을 받을 수 있다.
5. 클라 1은 느리게 받고, 2, 3은 빠르게 받는다.
아니면 주기적으로 원 서버의 리소스를 프록시 서버에 밀어 넣을 수 있다.
Cache-Control
캐시 지시어(directives) 기타
Cache-Control: public
- 응답이 public 캐시에 저장되어도 됨
Cache-Control: private
- 응답이 해당 사용자만을 위한 것임, Pivate 캐시에 저장해야 함(기본값)
Cache-Control: s-maxage
- 프록시 캐시에만 적용되는 max-age
Age: 00(초)(HTTP 헤더)
- 오리진 서버에서 응답 후 프록시 캐시 내에 머문 시간(초)
6. 캐시 무효화
Cache-Control
확실한 캐시 무효화 응답
Cache-Control: no-cache, no-store, must-revalidate(이렇게 세 개 통째로 보냄)
Pragma: no-cache (HTTP 1.0 하위 호환)
별도로 캐시를 적용하지 않아도 웹브라우저가 임의로 캐시할 수 있음.
근데 절대 캐싱하면 안되는 페이지, 리소스는 위의 캐시 무효화 응답을 해줘야함.
예) 통장 잔고 등(매번 갱신되어야 함)
캐시 지시어 - 확실한 캐시 무효화
no-cache
- 데이터 캐시해도 되는데 항상 원 서버에 검증(업데이트 됐는지 등)하고 사용
no-store
- 데이터에 민감한 정보가 있으므로 저장하면 안됨
- 메모리에서 사용하고 최대한 빨리 삭제
must-revalidate
- 캐시 만료 후 최초 조회 시 원 서버에 검증해야 함
- 원 서버 접근 실패 시 반드시 오류가 발생해야 함(504 Gateway Timeout)
- must-revalide는 캐시 유효시간이라면 캐시를 사용함
Pragma: no-cache
- 1.0 하위 호환. 옛날 브라우저 등 폴리필
no-cache 기본 동작
- 캐시 서버 요청 no-cache+ETag
- 프록시 서버에서 원서버로 전송
- 원서버 검증
- 원서버에서 프록시 캐시로 응답
- 프록시 캐시에서 브라우저로 응답(304)
- 브라우저 캐시로 저장
이때 프록시 캐시와 원 서버와의 네트워크가 단절되면
no-cache
인 경우, 프록시 캐시면 오류를 보내지 말고 오래된 데이터라도 보내게 설정 할 수 있음(Error or 200 OK)
근데 must-revalidate
가 있으면 프록시가 원서버에 접근할 수 없으면 항상 오류가 발생됨. (504 Gateway Timeout).
돈과 같은 결과는 오류가 나서 안 보이는 게 더 낫다.
'Programming > Computer Science' 카테고리의 다른 글
[HTTP] HTTP 일반 헤더, 표현, 콘텐츠 협상, 인증, 쿠키 (0) | 2022.04.17 |
---|---|
[HTTP] HTTP 상태코드, 리다이렉션 (0) | 2022.04.17 |
[HTTP] HTTP 메소드 활용, collection, store, POST 기반 설계, PUT 기반 설계 (0) | 2022.04.01 |
[HTTP] HTTP 메소드, GET, POST, PATCH, PUT, DELETE, 안전, 멱등 (0) | 2022.03.29 |
[HTTP] HTTP 메세지, stateless, 비연결성 (0) | 2022.02.04 |