인프런 - 모든 개발자를 위한 HTTP 웹 기본 지식에서 김영한 님이 강의해주는 내용을 바탕으로 정리를 진행했습니다. 강의를 직접 듣고 싶으신 분들은 하단 북마크를 눌러주세요.
*제 포스팅에 나오는 모든 이미지는 김영한 님께서 만든 강의 자료에서 가져왔습니다.
목차 보기
요약 정리
전체 내용을 보기 쉽게 요약 정리해둔 부분입니다. 자세히 알기 원하는 문장을 선택하시면 관련 섹션으로 이동합니다.
들어가며
이번 챕터에서는 캐시와 조건부 요청 관련 헤더들을 알아보겠습니다.
캐시 기본 동작
일단 캐시와 조건부 요청 관련 헤더들에 대해서 알아보기 전에 캐시의 기본 동작부터 알아보도록 하겠습니다.
상황) 서버로부터 이미지를 받아오는 웹 브라우저(캐시 미적용)
웹 브라우저에서 star.jpg 이미지를 요청해서 서버로부터 받아온다고 해봅시다. 웹 브라우저는 이미지를 요청하고 서버는 요청 메시지를 확인한 후에 star.jpg 이미지를 내려줄 겁니다.
이때, 서버의 응답 메시지에 0.1M의 HTTP 헤더가 있고, 1.0M의 HTTP 바디가 있다고 합시다. star.jpg를 담은 응답 메시지는 1.1M의 네트워크를 차지하면서 웹 브라우저에 내려오고 웹 브라우저에 표시될 겁니다.
상황) 서버로부터 이미지를 다시 받아오는 웹 브라우저(캐시 미적용)
클라이언트는 서버에 아까와 똑같은 요청을 보냅니다. 똑같은 요청을 받은 서버는 아까와 동일하게 헤더와 바디를 만들어서 응답 메시지를 내려줍니다. 즉, 1.1M 용량을 가진 이미지를 다시 내려주는 겁니다.
위의 상황들은 캐시를 가지고 있지 않습니다. 캐시가 없는 경우에는 똑같은 요청을 수십번 보내도, 데이터가 변경되지 않아도 네트워크를 통해서 데이터를 다운로드 받아야 합니다. 인터넷 네트워크는 PC 메모리, 하드 디스크에 비해서 매우 느리고 비쌉니다. 하지만 인터넷 네트워크를 통해서 데이터를 다운로드 받지 않으면 해당 데이터를 가져올 방법이 없기 때문에 매우 느리고 비싼 인터넷 네트워크를 계속 사용할 수 밖에 없습니다.
또한, 사용자 입장에서는 브라우저 로딩 속도가 느려진다는 단점이 있습니다. 결국 사용자는 느린 사용자 경험을 하게 됩니다.
그럼 캐시를 적용하면 어떻게 되는지 볼까요?
상황) 서버로부터 이미지를 받아오는 웹 브라우저(캐시 적용)
위의 상황과 동일하게 이미지를 받아옵니다. 웹 브라우저는 star.jpg 이미지를 서버에 요청하고 서버는 이전과 동일하게 헤더, 바디를 구성해서 웹 브라우저로 내려줍니다.
이때, 이전과 다른 점은 헤더에 있습니다. 헤더에 cache-control 이라는 헤더 필드를 추가해줍니다. 이전과는 다르게 서버에서는 일단 캐시와 관련된 걸 세팅해줘야 하는 겁니다. 그 부분이 바로 cache-control 입니다.
cache-control 은 캐시가 유효한 시간을 나타냅니다. max-age 가 60이라는건 60초동안은 해당 캐시가 유효하다는 뜻입니다.
응답 메시지로 웹 브라우저에 도착한 데이터는 웹 브라우저 내부에 캐시를 저장하는 캐시 저장소에 저장됩니다.
상황) 서버로부터 이미지를 다시 받아오는 웹 브라우저(캐시 적용)
2번째 요청이 일어나면 클라이언트는 먼저 캐시 저장소에서 캐시를 뒤집니다. 캐시를 확인해보니 아직 캐시 시간이 유효합니다. 그렇다면 클라이언트는 캐시에서 바로 데이터를 가져옵니다. 이렇게 되면 데이터를 다운로드 받기 위해서 네트워크를 이용할 필요가 없어집니다.
캐시를 적용하게 되면 캐시 덕분에 캐시 가능 시간동안 네트워크를 사용하지 않아도 됩니다. 따라서, 비싼 네트워크 사용량을 줄일 수 있게 되죠.
또한, 메모리 아니면 하드 디스크에서 데이터를 바로 불러오기 때문에 브라우저 로딩 속도가 매우 빠릅니다. 즉, 빠른 사용자 경험이 가능해지게 됩니다.
우리가 웹 브라우저를 사용할 때, 들어갔던 페이지에 다시 들어가게 되면 이전보다 빨리 로딩이 되는 걸 볼 수 있습니다. 이는 캐시가 적용되어 있기 때문에 그런겁니다.
상황) 서버로부터 이미지를 또다시 받아오는 웹 브라우저(캐시 적용)
이번에도 클라이언트는 이미지를 가져오려고 합니다. 클라이언트는 먼저 캐시 저장소를 뒤집니다. 근데 이번에는 캐시 시간이 초과되었습니다. 이전에 서버에서 응답 메시지를 보낼 때 해당 캐시를 60초만 사용할 수 있도록 했기 때문에 클라이언트는 더이상 해당 캐시를 사용할 수 없습니다.
그렇다면, 클라이언트는 이미지를 웹 브라우저에 띄워주기 위해서 서버에 이미지를 보내달라고 요청을 해야 합니다. 다시 요청을 하면 서버에서 이전과 동일하게 헤더와 바디를 구성하고 응답 메시지를 내려줍니다. 이전과 동일하게 cache-control 헤더 필드도 포함해줍니다.
클라이언트에서는 서버로부터 응답 데이터를 받고, 기존 캐시 데이터를 지우고 다시 캐시를 덮어 씌우면서 캐시를 초기화시킵니다. 캐시가 다시 60초동안 유효한 상태로 돌아가는거죠.
캐시 시간이 초과되면 당연히 서버에서 60초 동안만 사용하라고 한 것이기 때문에 60초가 지나면 신선하지 않은 데이터라고 생각해서 데이터를 서버로부터 다시 받아와야 합니다. 서버와 캐시에 있는 데이터가 틀어질 수 있기 때문에 그렇습니다.
서버를 통해서 데이터를 다시 조회하고 캐시를 새것으로 갱신해야 하는거죠. 이때 다시 네트워크 다운로드가 발생하게 됩니다.
근데, 방금 상황을 잘 생각해보면 star.jpg 이미지가 전혀 변하지 않았다는 걸 알 수 있습니다. 하지만 캐시를 갱신해야 하기 때문에 이미지 데이터 전체를 다시 다운로드 받았습니다.
클라이언트 캐시가 만료되었는데, 클라이언트가 가지고 있는 캐시 데이터와 서버 데이터가 같으면 다시 데이터를 받을 필요가 있을까요?
이걸 해결할 수 있는 매커니즘이 있습니다.
검증 헤더와 조건부 요청 ⓵
캐시 유효 시간이 지나서 서버로 다시 요청을 해야 할 시 두가지 상황이 나타납니다.
첫 번째 상황은 서버가 데이터를 노란색 별에서 초록색 별로 변경한 겁니다. 데이터가 변경되었기 때문에 캐시 저장소에 있는 데이터는 신선하지 못한 데이터라서 새로운 데이터를 다운로드 받아서 다시 캐시를 갱신해주어야 합니다.
두 번째 상황은 서버에 있는 데이터와 캐시 저장소에 있는 데이터가 동일한겁니다.
두 번째 상황일 경우, 처음부터 데이터를 다 다운로드받을 필요가 있을까요?
이 문제를 해결하는 것이 바로 “검증 헤더와 조건부 요청” 입니다.
예를 들어서 캐시가 만료되었다고 합시다. 클라이언트에서 서버로 데이터를 요청했는데, 서버가 이전에 보내준 데이터를 변경하지 않은겁니다. 즉, 현재 클라이언트의 캐시 저장소에 있는 데이터와 서버에 있는 데이터가 동일한 상태인거죠.
데이터를 처음부터 끝까지 다 전송하는 대신 저장해둔 로컬 캐시를 재사용 한다면 좋지 않을까요? 그렇다면, 클라이언트에 있는 캐시 데이터와 서버에 있는 데이터가 변하지 않았다는 사실을 확인할 방법이 필요합니다.
그 사실을 확인하는 방법으로 “검증 헤더”를 사용합니다.
상황) 서버로부터 이미지를 받아오는 웹 브라우저(검증 헤더 추가)
웹 브라우저가 서버에게 이미지를 요청했을 때, 서버는 캐시에 대한 정보를 설정해서 헤더와 바디를 내려줍니다. 이때 cache-control 헤더 필드와 함께 Last-Modified 헤더 필드를 추가로 보내줍니다.
Last-Modified 헤더 필드는 해당 데이터가 마지막으로 수정된 시간을 가지고 있습니다. 수정된 시간을 가지고 클라이언트로 내려간 데이터는 전과 동일하게 브라우저 캐시 저장소에 저장됩니다. 이때, 데이터 최종 수정일도 같이 캐시에 저장됩니다.
상황) 서버로부터 다시 이미지를 받아오는 웹 브라우저(검증 헤더 추가)
클라이언트가 다시 이미지를 웹 브라우저에 띄워야 합니다. 클라이언트는 먼저 캐시 저장소를 뒤집니다. 캐시 저장소 안에 이미지가 저장되어 있네요. 하지만 캐시 시간이 초과된 상태입니다.
캐시 데이터를 살펴보니 저장된 캐시 데이터에 데이터 최종 수정일이 들어있습니다. 데이터 최종 수정일을 웹 브라우저가 서버로 요청을 보낼 때, if-modified-since 헤더 필드에 넣어서 보내줍니다.
서버는 클라이언트가 보낸 요청을 받고, if-modified-since 헤더 필드가 있다는 걸 확인합니다. 그리고 서버가 가지고 있는 star.jpg 데이터의 최종 수정일과 비교합니다. 날짜를 사용해서 판단하고 날짜 데이터가 동일하다면 해당 캐시를 그대로 사용해도 된다고 판단합니다.
서버는 304 Not Modified 상태 코드와 함께 HTTP 바디없이 헤더만 클라이언트에게 내려줍니다. 수정된 부분이 없기 때문에 굳이 HTTP 바디를 포함할 필요가 없는 것이죠. 따라서, 시작 라인과 헤더만을 포함해서 응답 메시지를 클라이언트로 보내줍니다. 원래는 HTTP 바디까지 포함해서 총 1.1M의 응답 메시지가 클라이언트로 내려갔지만 헤더 부분만 전송함으로써 0.1M만 클라이언트로 전송되게 됩니다. 네트워크 부하가 확 줄어드는 겁니다.
클라이언트는 304 Not Modified 상태 코드가 내려온 것을 확인합니다. 해당 상태 코드를 통해서 캐시가 바뀌지 않았다는 걸 인식합니다. 클라이언트는 cache-control 헤더 필드를 확인하고 캐시 저장소에 있는 캐시를 다시 세팅합니다. 이 때, 캐시 데이터를 재사용하게 됩니다.
Last-Modified 검증 헤더에서 검증할 수 있는 값을 내려주고, if-modified-since 헤더를 통해서 조건부 요청을 보내서 서버에 있는 데이터가 갱신 되었는지 맞춰볼 수 있게 됩니다. 이렇게 하면 서버는 캐시를 재사용할 수 있을 때, HTTP 헤더만을 보내서 네트워크를 통해서 보내지는 데이터의 용량을 확 줄이게 됩니다. 그리고 로컬 캐시를 재사용할 수 있게 됩니다.
결과적으로 네트워크 다운로드가 발생하지만 용량이 적은 헤더 정보만을 다운로드하기 때문에 매우 실용적인 해결책입니다. 현재 웹 브라우저들은 대부분 이 매커니즘을 사용하고 있습니다.
웹 브라우저에서 캐시를 하고 있는 이미지를 하나 찾았습니다.
그리고 해당 이미지를 다시 불러오도록 했습니다. 클라이언트는 서버로 if-modified-since 헤더 필드를 통해서 데이터 최종 수정일을 보내줍니다.
해당 데이터는 서버에 있는 데이터와 캐시에 있는 데이터가 동일해서 서버에서 304 상태 코드를 보내주었습니다. 우리는 해당 데이터가 변경되지 않았고, 재사용해도 된다는 걸 알 수 있습니다.
검증 헤더와 조건부 요청 ⓶
이번에는 Last-Modified 를 보완하는 ETag 활용 방법을 알아봅시다.
먼저, 검증 헤더와 조건부 요청 헤더에 대해서 조금만 알아보겠습니다.
•
검증 헤더
검증 헤더는 캐시 데이터와 서버 데이터가 같은지 검증하는 데이터 입니다.
Last-Modified와 ETag가 그런 역할을 합니다. 검증 헤더를 활용해서 클라이언트는 서버로 요청 시에 조건부 요청 헤더를 만들어서 보낼 수 있게 됩니다.
즉, 해당 데이터가 맞는지, 안 맞는지 서버에게 물어볼 수 있게 되는거죠.
•
조건부 요청 헤더
조건부 요청 헤더는 검증 헤더로 조건에 따른 분기가 생깁니다.
검증 헤더로 Last-Modified 를 사용하는 경우에는 If-Modified-Since 를 조건부 요청 헤더로 사용하면 됩니다. ETag 를 사용하는 경우에는 If-None-Match 를 조건부 요청 헤더로 사용하면 되구요.
조건부 요청 헤더에 있는 날짜 정보와 서버가 가지고 있는 날짜 정보가 조건을 만족하면 200 이 상태 코드로 내려가게 되고, 아니면 304 가 상태 코드로 내려갑니다.
예를 들어서, If-Modified-Since 조건부 요청 헤더를 사용한다고 해봅시다.
If-Modified-Since 헤더는 “이후에 데이터 수정이 되었으면?” 이라는 의미를 가지고 있습니다. 해당 조건부 헤더에 클라이언트는 캐시가 가지고 있는 Last-Modified 헤더 정보를 넣어서 보내게 됩니다.
만약, 데이터가 변경되지 않았다면,
캐시가 가지고 있는 날짜 데이터와 서버가 가지고 있는 날짜 데이터가 같다면 서버 입장에서는 데이터를 변경할 필요가 없습니다. 즉, 데이터 수정에 실패한 거죠. 그렇기 때문에 200 상태 코드가 아닌 304 상태 코드를 내려주게 됩니다.
이전에 3xx 상태 코드에 대해서 배울 때([Network] HTTP 상태코드), 3xx 상태 코드들은 리다이렉션을 한다고 배웠을 겁니다. 그렇다면 이 상황에서는 어디로 리다이렉션을 하는 걸까요? Location 헤더도 없는데 말이죠. 304 상태 코드에서는 캐시로 리다이렉션을 합니다. 그리고 해당 캐시 데이터를 웹 브라우저에서 사용하게 됩니다.
만약, 데이터가 변경되었다면,
캐시가 가지고 있는 날짜 데이터와 서버가 가지고 있는 날짜 데이터가 다를겁니다. 서버는 데이터가 변경되었기 때문에 클라이언트가 가진 신선하지 않은 데이터를 수정해주어야 합니다. 즉, 데이터 수정에 성공한겁니다. 그렇기 때문에 200 상태 코드와 HTTP 바디까지 포함한 모든 데이터를 전송해줍니다.
총 1.1M 용량을 가진 데이터를 받기 위해서 네트워크 통신을 진행하는 겁니다.
이런 Last-Modified, If-Modified-Since 도 단점을 가지고 있습니다.
1.
1초 미만(0.x초) 단위로는 캐시 조정이 불가능합니다. 최대로 할 수 있는게 초 단위이기 때문에 그 미만으로는 조정이 불가능한거죠.
2.
날짜 기반의 로직을 사용합니다. 날짜 기반의 로직을 사용하면 해당 날짜 값이 현재 날짜와 다른지를 비교하기가 불편합니다.
3.
데이터를 수정해서 날짜 값은 다르지만 데이터 결과가 동일한 경우에도 네트워크를 통해서 데이터를 다운로드 받습니다.
예를 들어서, A라는 이미지를 B로 바꿨다가 A로 다시 변경했습니다. 결론적으로 A라는 이미지를 가지는건 동일한데, 날짜 데이터가 바뀐 겁니다.
클라이언트에서 서버로 요청했을 시에 서버 입장에서는 날짜가 갱신된 것이라 전체를 다 다시 다운로드해야하는 일이 생깁니다.
4.
서버에서 별도의 캐시 로직을 관리하고 싶지만 해당 검증 헤더는 시간으로 비교하기 때문에 다른 로직으로는 비교가 불가능합니다.
예를 들어서, 스페이스나 주석처럼 크게 영향이 없고 중요하지 않은 변경에서는 캐시를 유지하고 싶다면 서버에서 완전하게 캐시 메커니즘을 컨트롤할 수 있는 방법을 사용해야 합니다.
서버에서 완전하게 캐시 메커니즘을 컨트롤할 수 있는 방법이 바로 ETag 를 활용하는 방법입니다.
ETag 는 캐시용 데이터에 임의의 고유한 버전 이름을 달아둡니다. Last-Modified 처럼 날짜 데이터를 붙이는 것이 아니라 서버에서 임의의 이름을 붙여두는 겁니다.
예를 들어서, 버전을 이름으로 사용할 수도 있고, 해시 결과를 사용해서 이름을 붙일 수도 있습니다.
해시 결과는 해시 알고리즘을 통해서 받은 결과입니다. 해시 알고리즘은 파일이 동일하면 같은 결과값을 냅니다. 파일 컨텐츠가 같으면 똑같은 해시값을 내보내는겁니다. 조금이라도 파일의 내용이 다르면 완전히 다른 해시값을 생성해서 내보냅니다. 해시 알고리즘을 사용하게 되면 원본을 조금 수정했다가 다시 원본과 동일하게 만들었을 시에 원본과 같은 해시값을 가지게 됩니다.
ETag 는 단순하게 ETag 값만 보내서 같으면 유지하고 다르면 다시 받으면 됩니다. 날짜처럼 해당 날짜가 이전인지, 이후인지를 계산하지 않아도 됩니다.
상황) 서버로부터 이미지를 받아오는 웹 브라우저(ETag 활용)
이전처럼 서버로부터 이미지를 받아온다고 합시다. 서버는 요청을 받아서 이미지를 내려줄 때, ETag 검증 헤더를 포함해서 내려줍니다.
클라이언트는 응답 메시지를 받아서 가져온 데이터를 웹 브라우저 캐시 저장소에 저장합니다. 그리고 ETag 를 함께 넣어둡니다.
상황) 서버로부터 다시 이미지를 받아오는 웹 브라우저(ETag 활용)
클라이언트가 서버로부터 다시 이미지를 받아온다고 합시다. 클라이언트는 일단 캐시 저장소에 들러서 캐시를 뒤집니다. 아쉽게도 캐시 시간이 초과되었습니다. 클라이언트는 캐시가 가지고 있는 ETag 정보를 가지고 If-None-Match 헤더 필드에 넣어서 보내줍니다.
서버는 클라이언트에서 보낸 요청 메시지를 확인합니다. If-None-Match 헤더 필드에 있는 ETag 정보와 서버 데이터가 가지고 있는 ETag 정보가 동일하기 때문에 None-Match 되어 버렸군요. 데이터가 수정되지 않았습니다.
데이터 수정에 실패했기 때문에 304 상태 코드를 웹 브라우저로 보냅니다. 데이터 변경이 일어나지 않았기 때문에 HTTP 바디도 포함하지 않습니다. 헤더만 보내주는 겁니다.
응답 메시지를 받은 클라이언트는 받은 헤더 정보를 가지고 캐시 저장소를 갱신합니다. 물론, 캐시 데이터는 재사용됩니다.
ETag와 If-None-Match를 사용하는 방식은 캐시 제어 로직을 서버에서 완전히 관리할 수 있습니다. 이렇기 때문에 클라이언트 입장에서는 블랙 박스가 되어 버리는 겁니다. 클라이언트는 서버가 가지고 있는 캐시 메커니즘을 모르는 상태로 단순히 ETag 값만 보내주게 됩니다.
예를 들어서, 애플리케이션 배포 주기에 맞춰서 ETag를 모두 갱신하는 것이 가능합니다. 캐시 제어 로직을 서버에서 완전히 관리하니깐요.
캐시와 조건부 요청 헤더
캐시 제어 헤더는 cache-control, pragma, expires 가 있습니다. 그 중 pragma, expires는 하위 호환을 위해서 사용하기 때문에 크게 중요하지 않고, cache-control 이 중요한 역할을 맡고 있습니다.
Cache-Control
Cache-Control은 캐시 지시어 입니다. cache-control 헤더 필드에 들어가는 헤더 벨류에 따라서 캐시에 대해서 보내지는 내용이 달라집니다.
•
max-age
예시에서 계속 봤던 부분입니다. max-age 은 캐시가 얼마동안 유효한지, 초 단위로 나타냅니다.
예시에서는 60초로 정해뒀지만, 보통은 60초보다 길게 잡습니다.
•
no-cache
no-cache 는 캐시를 하지 말라는 뜻입니다. 하지만 데이터는 캐시해도 됩니다.
대신, 캐시 사용 전에 If-Modified-Since, If-None-Match 조건부 요청을 사용해서 원(Origin) 서버에 로컬 데이터가 변경되었는지 항상 검증해야 합니다.
항상 서버에 검증을 하고 캐시 데이터를 사용하면 됩니다. 서버에 보냈는데 서버가 데이터가 바뀌었다고 응답을 보내면 캐시를 바꾸면 되고, 아니면 로컬에 저장된 캐시 데이터를 사용하면 됩니다.
근데 왜 원(Origin) 서버까지 가서 검증을 해야하는 건가요?
중간에 프록시 캐시 서버가 존재합니다. 해당 서버를 거쳐서 진짜 서버, 즉 원 서버에 접근할 수 있습니다.
그렇기 때문에 중간에 있는 프록시 캐시 서버에서 검증을 해주면 안되고, 원 서버까지가서 검증을 해야 합니다.
•
no-store
no-store 는 데이터에 민감한 정보가 있으므로 저장을 하면 안된다는 뜻을 가지고 있습니다. HTTP 응답 코드에 no-cache 라는 단어가 있으면 진짜로 저장을 하면 안됩니다.
보통 캐시를 하면 하드 디스크같은 곳에 저장이 되는데, 이 경우에는 메모리에서 사용하고 최대한 빨리 삭제해야 합니다.
Pragma
Pragma 는 캐시 제어 하위 호환 버전입니다.
no-cache 를 사용할 수 있고, cache-control 에서의 no-cache 와 동일하게 동작합니다. 하지만, Pragma 가 HTTP 1.0 하위 호환 버전이기 때문에 지금은 거의 사용하지 않습니다.
만약, 하위 호환때문에 필요하다면 사용하시면 됩니다.
Expires
Expires 는 캐시 만료일을 지정하는 헤더로 하위 호환 버전에서 사용합니다.
max-age 처럼 초로 계산하는 것보다는 expires 처럼 정확한 날짜로 사용하는 것이 더 좋지 않나요?
아닙니다. 초로 계산을 하면 지금부터 몇 시까지의 시간을 계산해서 초로 넣으면 되지만, 날짜는 날짜 하나 하나를 다 따져봐야 합니다.
즉, 초를 사용했을 때 훨씬 유연하게 사용 가능합니다.
현재 cache-control 헤더 필드에서는 max-age 사용을 권장하고 있고, max-age와 expires를 함께 사용했을 때는 expires 는 무시됩니다.
프록시 캐시
이전에도 설명했지만 Origin 서버는 진짜 서버입니다. 하지만, 원 서버가 항상 우리 근처에 있지만은 않습니다.
클라이언트는 한국에 있지만, 원 서버는 미국에 있을 수도 있는거죠.
한국에 있는 클라이언트가 서버에서 데이터를 요청했을 때, 원 소스가 있는 원 서버까지 갔다오려면 많은 시간이 걸립니다. 미국에 있는 원 서버까지 들렸다가 다시 돌아와야 하니깐요.
그러면 한국에 있는 클라이언트들은 모두 500ms를 기다리게 됩니다. 이미지 하나를 다운받기 위해서 0.5초를 기다리는 거죠.
그래서 이런 문제를 해결하기 위해 프록시 캐시 서버를 도입하게 됩니다.
프록시 캐시 서버는 지리적 제약 없이 전 세계 사용자에게 빠르고 안전하게 콘텐츠를 전송할 수 있는 콘텐츠 전송 기술, CDN(Content Delivery Network) 서비스를 제공해줍니다.
미국에 있는 원 서버에서는 한국에 있는 클라이언트에게 응답을 보낼 때 속도가 느릴 수 밖에 없습니다. 따라서, 한국 어딘가에 프록시 캐시 서버를 넣어두고 요청 시 웹 브라우저의 목적지 IP를 조작해서 원 서버로 요청을 보내는 것이 아니라 프록시 캐시 서버로 접근하게 만드는 겁니다. 그렇게 되면 브라우저에서 보낸 요청이 프록시 캐시 서버를 거쳐서 미국에 있는 원 서버로 가게 됩니다.
흐름을 간단하게 설명하자면:
1.
클라이언트에 있는 DNS 등의 정보를 확인해봤더니, 프록시 캐시 서버로 요청을 하도록 되어 있습니다.
2.
프록시 서버를 목적지 IP로 설정해서 요청을 보냅니다.
3.
프록시 캐시 서버는 응답 시간이 빠르기 때문에 이전에는 0.5초가 걸렸던 응답을 0.1초만에 해줍니다.
즉, 프록시 캐시 서버를 도입하면서 빠른 응답이 가능해집니다.
우리가 유튜브를 볼 때 사람들이 잘 보지 않는 외국 콘텐츠를 본다면 유튜브 다운로드 속도가 느린다고 느껴질 겁니다. 반대로 사람들이 많이 보는 콘텐츠를 누르면 로딩 속도가 빠릅니다.
그 이유는 원 서버에 있는 원 소스를 한국 어딘가에 있는 프록시 캐시 서버에 다운로드 받아두기 때문입니다. 최초에 해당 콘텐츠를 받아올 때에는 프록시 캐시 서버에 해당 데이터가 없기 때문에 로딩 시간이 걸립니다. 하지만 한 번 다운로드 받아두고나면 두번째 유저부터는 빠르게 조회가 가능합니다.
프록시 캐시 서버는 public 캐시이기 때문에 공용으로 사용하는 데이터를 저장합니다. 웹 브라우저 같은 경우에는 private 캐시이기 때문에 웹 브라우저 캐시 저장소나 로컬 PC에 저장합니다.
Cache-Control
Cache-Control 은 프록시 캐시 관련 캐시 지시어도 가지고 있습니다.
•
public
HTTP 응답에다가 public 를 넣어서 보내게 되면, 응답이 public 캐시에 저장되어도 된다는 뜻을 가집니다.
•
private
HTTP 응답에 private 를 넣어서 보내게 되면, 응답이 해당 사용자만을 위한 것임을 나타냅니다.
따라서, 프록시 캐시 서버는 응답을 캐시할 수 없습니다. 해당 응답은 private 캐시에서만 저장이 가능하기 때문입니다.
로그인 사용자 정보가 public 캐시에 저장되게 된다면 큰일이겠죠?
•
s-maxage
프록시 캐시에만 적용되는 max-age 입니다.
•
Age
원 서버에서 응답 후 프록시 캐시 내에 머문 시간을 나타냅니다.
캐시 무효화
cache-control 를 사용해서 확실하게 캐시 무효화를 할 수 있는 응답이 있습니다.
애초에 캐시를 적용하지 않으면 캐시가 안되는 거 아닌가요?
아닙니다. 캐시를 적용하지 않아도 웹 브라우저들이 GET 요청을 한 경우에는 임의로 캐시합니다.
따라서, 진짜 캐시를 하면 안되는 정보는 캐시 무효화를 통해서 캐시 안되도록 해주어야 합니다.
예를 들어서, 현재 사용자의 통장 잔고가 있다고 해봅시다. 해당 데이터를 응답 시에 캐시하게 된다면 절대 안되겠지요?
확실한 캐시 무효화를 하고 싶다면 이렇게 작성하면 됩니다.
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Plain Text
복사
cache-control 까지만 써도 되겠지만 혹시 HTTP 1.0에서 요청이 올 수도 있기 때문에 과거 브라우저를 대응하기 위해서 pragma 도 추가합니다.
이렇게 작성해주면 확실하게 대응이 가능합니다.
•
no-cache : 데이터는 캐시해도 되지만, 항상 원 서버에 검증하고 사용
•
no-store : 데이터에 민감한 정보가 있으므로 저장하면 안됨(메모리에서 사용하고 최대한 빨리 삭제)
•
must-revalidate : 캐시 만료 후 최초 조회시 원 서버에 검증해야 함
•
pragma: no-cache : HTTP 1.0 하위 호환
캐시 지시어에 들어가는 내용 중에 중복된다고 느끼는 값들이 있을겁니다.
바로, no-cache와 must-revalidate 입니다.
왜 no-cache 만 하면 되지, must-revalidate 를 추가로 하나요?
비슷하게 기능하지만 차이점이 존재합니다.
상황) 서버로부터 다시 이미지를 받아오는 웹 브라우저(no-cache 사용)
1.
클라이언트가 캐시 서버로 요청을 보내면 no-cache 를 넣어뒀기 때문에 프록시 캐시는 자신이 요청을 처리하면 안된다는 걸 알고 원 서버에 요청을 보냅니다.
2.
원 서버에서는 해당 캐시 요청을 검증하고 응답을 내려줍니다.
3.
해당 캐시가 변경되지 않았기 때문에 304 Not Modified 응답이 내려옵니다.
4.
클라이언트가 응답 데이터를 보고 캐시 데이터가 변경되지 않은 걸 확인합니다.
5.
웹 브라우저는 캐시 저장소에서 캐시 데이터를 조회해서 사용합니다.
no-cache 를 사용하면 이런 식으로 클라이언트와 서버가 통신하게 됩니다. 하지만 프록시 캐시와 원 서버 사이에서 문제가 발생했을 경우에 no-cache 사용 흐름에서도 문제가 생깁니다.
상황) 원 서버와 프록시 캐시 서버 사이에 문제가 있는 상태에서 다시 이미지를 받아오는 웹 브라우저(no-cache 사용)
1.
클라이언트가 캐시 서버로 요청을 보내면 no-cache 를 넣어뒀기 때문에 프록시 캐시는 자신이 요청을 처리하면 안된다는 걸 알고 원 서버에 요청을 보냅니다.
2.
근데 순간적으로 네트워크 단절이 발생해서 원 서버에 접근이 불가능합니다.
3.
프록시 캐시 서버는 무언가 장애가 난 걸 보여주기 보다는 옛 데이터라도 보여주자라고 설정되어 있을 경우, 옛 데이터를 웹 브라우저로 보냅니다.(오류 혹은 오래된 데이터)
4.
200 상태 메시지와 함께 프록시 캐시에 있는 예전 데이터가 내려갑니다.
그렇다면 같은 상황에서 must-revalidate 를 사용하면 어떻게 될까요?
상황) 원 서버와 프록시 캐시 서버 사이에 문제가 있는 상태에서 다시 이미지를 받아오는 웹 브라우저(must-revalidate 사용)
1.
클라이언트가 캐시 서버로 요청을 보내면 must-revalidate 를 넣어뒀기 때문에 프록시 캐시는 자신이 요청을 처리하면 안된다는 걸 알고 원 서버에 요청을 보냅니다.
2.
근데 순간적으로 네트워크 단절이 발생해서 원 서버에 접근이 불가능합니다.
3.
원 서버에 접근할 수 없는 경우, 항상 오류가 발생해야 하기 때문에 504 Gateway Timeout 이 응답으로 내려갑니다.
4.
클라이언트는 504 상태 코드를 보고 프록시 캐시 서버와 원 서버 사이에 문제가 있음을 확인합니다.
만약, no-cache 처럼 프록시 캐시 서버와 원 서버 문제를 해결했다면 어떻게 해야할까요?
예를 들어서, 돈을 입금하고 나서 통장 잔고를 확인하는데, 원 서버 접근이 끊겼다고 합시다.
이 경우에 원 서버 접근이 끊겼기 때문에 오류창이 떠야 하는데, 프록시 캐시에서 예전 데이터를 보내준다면 사용자는 과거 데이터를 보고 혼란스러울 겁니다. 입금을 했는데 입금한 금액이 반영이 안된 상태로 금액이 보여지니깐요.
이런 부분을 확실하게 해결하기 위해서 no-cache 와 must-revalidate 를 함께 넣어주어야 하는 겁니다.