이번 시간에는 캐시에 대해서 알아보겠습니다.
먼저 캐시가 없는 초기 GET 요청부터 순차적으로 살펴보겠습니다.

초기 GET 요청을 통해 서버로 부터 star.jpg 라는 이미지 파일을 받아옵니다.
이때 star.jpg를 받기 위한 http 메시지의 크기는 헤더의 크기 0.1M / 바디의 크기는 1.0M 입니다.
star.jpg에 대한 실질적인 내용은 바디에 담겨있기 때문에, 바디의 크기가 더 클 수 밖에 없습니다.
그렇게 되면, 브라우저는 서버에서 보낸 star.jpg에 대한 http 메시지를 받아 렌더링하고 이미지를 인식합니다.
이후 브라우저(클라이언트)가 다시 star.jpg를 다운로드 받아 사용해야할 일이 있어, 요청을 또 보냅니다.
캐시의 역할은 지금부터 나타납니다.
만약 캐시의 개념이 없는 경우라면 위 그림과 같이, 1.1M크기의 http 메시지를 다시 받아 렌더링합니다.
하지만 캐시의 개념이 있다면, 초기 전송에서

아래와 같이 cache-control 이라는 헤더를 추가하여 max-age (캐시 유효기간)을 설정하여 전달하게 됩니다.
이 메시지를 받은 star.jpg는 웹 브라우저의 캐시 저장소에 이를 저장합니다.
그리고 star.jpg를 또 사용할 일이 있어 서버에 GET 요청을 다시 보내야 하는 경우.

서버에 요청하기 전에 브라우저 캐시 저장소를 먼저 살펴보고 유효기간 내라면 서버에 요청하지 않고, 캐시 저장소에서
가져다가 씁니다. 이렇게 되면 서버에서 1,1M의 메시지를 다시 다운받는 번거로움을 줄여 효율적이게 됩니다.
이 예시를 통해 알 수 있듯 캐시의 장점으로는
- 캐시 덕분에 캐시 가능 시간동안 네트워크를 사용하지 않아도 된다.
- 비싼 네트워크 사용량을 줄일 수 있다.
- 브라우저 로딩 속도가 매우 빠르다
고 할 수 있습니다.
그렇다면 만약 캐시의 유효시간이 끝났다면?
캐시를 사용할 때 헤더파일에 cache-control : max-age=60 이라는 캐시의 유효시간 설정을 볼 수 있었습니다.
이는 캐시의 유효시간이 60초라는 뜻이고, 60초 이후에는 사용할 수 없다는 뜻입니다.
그럼 캐시 유효시간이 끝나고 나면 다시 다운받아야하나요?
아닙니다. 60초 내에 star.jpg의 내용이 바뀔 수도 있지만 그렇지 않을 수도 있습니다.

이러한 점을 잘 이용하기 위해, 첫 요청 때 헤더를 하나 더 추가하여 GET요청에 대한 응답 메시지를 보내줍니다.
추가할 헤더는 'Last-Modified: 시간' 으로 마지막으로 수정된 시간을 나타냅니다.
그리고 두번째 요청을 보낼 때 서버에 아래 이미지와 같은 요청을 보냅니다.

첫 요청 때 받은 Last-Modified 헤더를 2번째 요청 헤더에 추가하여
"내가 캐시에 가지고 있는 star.jpg의 마지막 수정일은 2020년 11월 10일 10시0분0초야" 라는 내용을 보냅니다.

만약 내가 비록 유효기간이 지난 캐시를 가지고 있지만, 그 캐시에 있는 star.jpg가 서버에 있는 star.jpg와 같다면
서버에서는 "브라우저 너가 캐시 저장소에 가지고 있는 star.jpg랑 내가 가지고 있는거랑 똑같아!" 라고 인식합니다.

그러면 서버에서는 앞선 시간에 배웠던 304 상태코드와 함께. cache-control과 Last-Modified 헤더를 다시 보내
"그냥 너가 캐시 저장소에 가지고 있는 star.jpg 써도 돼"라는 뜻의 http 메시지를 보내줍니다.
(*중요*: 이 때 star.jpg의 실질적 데이터는 캐시 저장소에 있으므로, HTTP BODY는 보내지 않습니다.)
HTTP BODY는 보내지 않게 때문에 0.1M의 헤더만 전송하면 되고, 이 헤더를 받은 브라우저는
기존에 캐시 저장소에서 가지고 있던 캐시 유효기간이 만료된 star.jpg의 유효기간과 최종 수정일을 갱신합니다.
만약, 캐시 유효기간이 끝났고 '최종 수정일'이 다르다면, 첫 요청과 같이 1.1M의 http메시지를 받아와야 합니다.
즉, 정리하자면
캐시를 사용하면 같은 데이터에 대해서 다수의 요청을 해야할 경우, 캐시 저장소에서 가져다 쓰면 되기 때문에
네트워크 자원 절약, 빠른 로딩이 가능하며
만약 캐시 유효기간이 끝난다 하더라도 재사용 할 수 있는 메커니즘이 있기 때문에 굉장히 효율적입니다.
또한 캐시가 끝났을 때 이 캐시를 사용해도 되는지 안되는지 확인하는 Last-Modfied헤더를 검증 헤더라고 합니다.
이 역시도, 아쉬운 점이 있는데 서버에서 별도의 캐시 로직을 관리하고 싶은 경우.
예를 들면, 스페이스바(공백) 또는 주석의 추가,제거 등은 실질적으로 데이터에 영향이 없는 변화이기 때문에
이를 last-modifed (수정)이라고 인식하고 싶지 않아할 때가 있습니다.
이럴 경우에는 ETag라는 조건부 요청을 통해 해결 할 수 있습니다.
ETag(Entity Tag)는 캐시용 데이터에 임의의 고유한 버전 이름을 달아두는 것으로
ex) ETag: "a2jiodwjdjsdf" 와 같이 고유 이름을 달아주고 데이터가 변경 될 시 Tag의 value 값을 바꿔줍니다.
예시를 통해 알아보겠습니다.

Last-Modfied 대신 ETag 헤더가 들어간 것을 볼 수 있습니다.
첫 요청시 브라우저(클라이언트)에 ETag 값을 설정하여 전송하고 이를 브라우저는 캐시 저장소에 저장합니다.

그리고 만약 캐시 유효기간이 지난 후 요청을 다시 할 땐, if-None-Match 헤더에 ETag 값을 넣어 서버에 전송합니다.

그러면 서버에서는 브라우저가 보낸 ETag 값과 서버에서 가지고 있는 ETag 값이 일치하는지 확인하고
일치하다면 304 상태코드와 함께 HTTP BODY를 제외한 http 메시지를 반환합니다.
서버에서 응답을 받은 브라우저는 기존 캐시를 재사용해도 됨을 인식하고 캐시 유효기간을 갱신합니다.
먄약, ETag 값이 다르다면, 첫 요청 처럼 HTTP BODY를 포함한 HTTP 메시지를 받아야 합니다.
캐시 제어 헤더
이제 캐시에 대해서는 어느정도 알아 봤으니, 캐시 제어 헤더에 대해 알아보겠습니다.
캐시 제어 헤더에는
- Cache-Control : 캐시 제어
- Pragma: 캐시 제어(하위 호환) -> 거의 사용 x
- Expires: 캐시 유효 기간(하위 호환)
이 있습니다
1. Cache-Control: 캐시 제어
- Cache-Control : max-age => 캐시 유효 시간, 초단위
- Cache-Control : no-cache => 데이터를 캐시해도 되지만, 항상 원 서버에 검증하고 사용해라.
- Cache-Control: no-store => 데이터에 민감한 정보가 있으므로 저장하면 안됨.
2.Expries :캐시 만료일 지정
-ex) expires: Mon, 01, Jan 1990 00:00:00 GMT
-캐시 만료일을 정확한 날짜로 지정
-지금은 더 유연한 Cache-Control의 max-age(초단위) 를 권장
-만약 Cache-Control과 함께 사용된다면 Expires는 무시된다.
조건부 요청/검증 헤더
검증 헤더도 정리해보자면
- ETag -> 위 예시 참고
- Last-Modfied -> 위 예시 참고
조건부 요청 헤더
- If-Match -> ETag 값 사용 (ETag값이 서버와 일치하다면)
- If-None-Match-> ETag 값 사용 (ETag값이 서버와 일치하지 않는다면)
- If-Modified-Since -> Last-Modfied 값 사용 (Last-Modified 값이 서버와 일치하다면)
- If-Unmodified-Since-> Last-Modfied 값 사용 (Last-Modified 값이 서버와 일치하지 않는다면)
프록시 캐시
이번엔 프록시 캐시에 대해서 알아보겠습니다.
프록시 캐시는 일종의 중간다리 입니다.
우리가 흔히 사용하는 어플리케이션 중 하나인 '유튜브'를 예시로 들면
유튜브의 본사는 한국이 아닌 미국이 본사입니다.
즉 미국에 있는 원 서버에 접근하려면, 다양한 네트워크 계층을 타고 멀리 있는 서버에 접근해야하므로
시간이 많이 소요됩니다,

하지만 프록시 캐시 서버를 이용한다면, 접근이 조금 더 빨라질 수 있습니다.
프록시 캐시 서버는 원래 리소스가 있는 원 서버(미국) 에서 클라이언트(한국)으로 데이터를 보낼 때
중간에서 일종의 캐시 저장소 역할을 해줍니다.
클라이언트들이 사용하려고 요청한 데이터 들을 프록시 캐시 서버에 저장해두고,
한국에 있는 다른 클라이언트가 그 데이터를 요청할 때는 원서버에서 받아오는 것이 아닌
한국에 있는 프록시 서버에서 받아옵니다.

유튜브를 계속해서 예를 들면, 한국에서 A 사용자가 "ABC 영상을 시청한다면"
ABC영상을 한국에서 처음으로 시청하는 A 사용자는 원 서버에서 영상을 받아와야 하기 때문에
체감할 정도는 아니지만 시간이 걸릴 수 있습니다.
하지만 A 사용자가 영상을 받아오며 프록시 캐시 서버에도 그 영상이 캐시 처럼 저장이되고,
B사용자 C사용자 등 그 외의 사용자들이 "ABC 영상"을 시청할 때는 A 사용자보다 더 빠르게 영상에
접근이 가능합니다.
하지만 이 프록시 캐시 서버에도 모든 데이터들을 캐시할 수 있는건 아닌데요.

프록시 캐시에서의 캐시 지시 헤더는 위와 같습니다.
프록시 캐시에는 public한 캐시만 저장할 수 있고, private 와 같은 헤더가 적용되어 있다면
저장할 수 없습니다.
캐시 무효화
마지막으로 캐시 무효화에 대해 알아보겠습니다.
우리가 캐시를 적용하지 않아도 웹브라우저에서는 GET 요청시 임의로 캐시하는 경우도 있습니다.
하지만 정말로 캐시를 하면 안되는 페이지가 있을 수 있는데, 이를 위해서 캐시 무효화 설정이 필요합니다.
캐시 무효화를 위해서 사용하는 헤더에는
- Cache-Control: no-cache, no-store, must-revalidate
- Pragma: no-cahe (Pragma는 자주 사용되지 않지만 혹시모를 요청 대비)
이 모든 내용을 포함해준다면, 확실한 캐시 무효화가 이루어집니다.
Cache-Control에서는
no-cache / no-store / must-revalidate가 있는데
- Cache-Control: no-cache => 데이터는 캐시해도 되지만, 항상 원 서버에 검증
- Cache-Control: no-store => 데이터에 민감한 정보가 있으므로 저장 x (메모리에서 사용 후 최대한 빨리 삭제)
- Cache-Control: must-revalidate => 캐시 만료 후 최초 조회시 원 서버에 검증해야함
- 만약 원서버에 접근 실패시 504(Gateway-Timeout) 오류 발생
각각 이러한 의미를 가지고 있습니다.

no-cache의 기본 동작을 살펴보면 브라우저에서 캐시를 사용할 경우 no-cache 헤더와 함께 ETag를 헤더에 담아
프록시 캐시에 검증 요청을 보냅니다. 프록시 캐시는 다시 원 서버에 검증 요청을 보내고
검증이 완료 된 캐시는 304 응답과 함께 프록시 캐시-> 웹 브라우저로 반환되어 검증 후 사용되게 됩니다.
하지만 중간에 원서버와의 연결이 원활하지 않은 경우 또는 접속이 끊긴 경우

원서버에 접근이 불가능 해 검증이 안되므로 오류가 발생했다는 메시지를 보여주는 것 보다는
만약 캐시가 원서버와 다르더라도 기존에 가지고 있던 캐시를 사용하는게 낫겠다고 판단하여
프록시 캐시에서 임의로 200 OK 상태를 반환해줍니다.
만약, 은행 계좌 잔고를 생각했을때를 생각하면 이는 심각한 오류일 수 있습니다.
(잔고 100만원에서 50만원을 송금한 후 잔액이 50만원이 돼야하는데, 위와 같은 오류로 계속해서 100만원이 보이는 경우)
이를 방지하기 위해
Cache-control: must-revalidate를 사용합니다.

must-revalidate는 원 서버에 접근할 수 없는 경우 504 오류 메시지를 반환하여, 프록시 캐시가 임의로
200 OK 상태를 내려주는 것을 방지합니다.
'HTTP' 카테고리의 다른 글
| HTTP 헤더 (http 김영한) (1) | 2024.01.07 |
|---|---|
| HTTP 상태코드 (http 김영한) (0) | 2024.01.07 |
| HTTP API 설계 (GET/POST/PUT/PATCH/DELETE) (김영한 HTTP) (0) | 2024.01.06 |
| HTTP 서버 (김영한 HTTP) (0) | 2024.01.06 |
| HTTP URI/URL/URN (김영한 http) (1) | 2024.01.06 |