리버스 프록시 캐시
고친 사람 github-actions[bot]
리버스 프록시 캐시는 서버가 한 번 만든 응답을 서버 앞에 붙잡아 두고 다음 요청에 대신 내주는 일입니다. 같은 화면을 찾는 요청이 여러 번 와도 서버는 그 화면을 한 번만 만들면 됩니다. 이 캐시는 서비스를 운영하는 쪽이 자기 서버 앞에 직접 세웁니다. 그래서 무엇을 얼마나 담아 둘지도 운영하는 쪽이 정합니다.
쉽고 빠른 이해
리버스 프록시 캐시는 서버 바로 앞에서 응답을 받아 두었다가 같은 요청에 대신 답합니다. 쇼핑몰 첫 화면은 누가 열어도 같으므로 한 번 만든 것을 모든 방문자에게 돌려 쓸 수 있습니다.
이게 없으면 방문자가 올 때마다 서버가 같은 화면을 처음부터 다시 만듭니다. 방문자가 몰리면 서버가 그 반복에 먼저 지칩니다.
- 방문자의 요청이 서버보다 캐시에 먼저 닿습니다
- 같은 요청의 응답을 담아 두었고 아직 쓸 만하면 그것을 바로 내줍니다
- 없거나 낡았으면 서버에서 새로 받습니다. 담아도 되는 응답이면 다음 요청에 쓰려고 남깁니다
대가는 낡은 내용과 남의 화면이 새는 위험입니다. 서버에서 내용을 고쳐도 캐시에 든 옛 응답이 한동안 나갑니다. 사람마다 달라야 할 화면을 잘못 담으면 다음 방문자가 남의 화면을 봅니다.
상세
빵집을 떠올려 봅니다. 손님이 식빵을 찾을 때마다 제빵사가 반죽부터 새로 하지 않습니다. 처음 찾은 손님에게 줄 식빵을 구우면서 몇 개를 더 구워 계산대 앞 진열대에 올려 둡니다. 다음 손님이 찾으면 거기서 꺼내 줍니다. 진열대는 빵집 주인이 직접 채우고 직접 비웁니다.
캐싱은 한 번 얻은 결과의 복사본을 가까운 곳에 두고 다음번에 다시 얻지 않는 일입니다. 복사본을 담아 두는 저장소를 캐시라고 부릅니다. 비싼 계산이나 먼 곳까지 다녀오는 일을 줄이려고 씁니다.
리버스 프록시는 서버 앞에 서서 요청을 대신 받는 중개자입니다. 클라이언트는 이 중개자를 서버로 알고 요청을 보냅니다. 중개자는 요청을 뒤쪽 서버에 넘기고 돌아온 응답을 클라이언트에게 돌려줍니다.
뒤쪽에서 응답을 처음 만들어 내는 서버를 오리진 서버라고 부릅니다. 백엔드 개발자가 만든 애플리케이션 서버가 대개 이 오리진 서버입니다.
리버스 프록시 캐시는 리버스 프록시가 지나가는 응답을 캐시에 담아 두는 것입니다. 다음에 같은 요청이 오면 오리진 서버에 넘기지 않고 담아 둔 응답으로 답합니다. 오리진 서버는 그 요청이 왔다는 것조차 모릅니다.
앞의 빵집에 대면 진열대가 리버스 프록시 캐시이고 제빵사가 오리진 서버입니다. 진열대를 채우고 비우는 주인은 이 캐시를 세운 서비스 운영자입니다.
이 캐시가 다루는 것은 대개 HTTP(HyperText Transfer Protocol, 하이퍼텍스트 전송 프로토콜) 요청과 응답입니다. HTTP 는 브라우저와 서버가 웹 페이지를 주고받을 때 쓰는 약속입니다.
HTTP 요청과 응답에는 본문 말고 헤더가 따로 붙습니다. 헤더는 이름과 값을 짝지어 적은 줄입니다. 캐시는 본문을 읽지 않고 이 헤더를 보고 응답을 담을지와 얼마나 쓸지를 판단합니다.
요청이 지나는 경로 위의 위치
브라우저가 보낸 요청은 오리진 서버에 닿기 전에 캐시를 여럿 지날 수 있습니다. 리버스 프록시 캐시는 그중 오리진 서버에 가장 가까운 쪽에 섭니다. 아래 그림은 한 요청이 지나는 순서를 위에서 아래로 늘어놓은 것입니다.
flowchart TD
U["사용자 브라우저"] --> B["브라우저 캐시"]
B --> N["인터넷"]
N --> R
subgraph S["서비스 운영자가 관리하는 구간"]
R["리버스 프록시 캐시"] --> O["오리진 서버"]
end
브라우저 캐시는 사용자 한 사람의 기기에 붙은 캐시입니다. 리버스 프록시 캐시는 요청이 인터넷을 건너 서비스 쪽 구간에 들어선 뒤에 만납니다. 브라우저 캐시는 사용자가 쥡니다. 리버스 프록시 캐시는 서비스 운영자가 쥡니다.
캐시 적중과 캐시 미스
캐시는 요청을 받으면 먼저 같은 요청의 응답을 담아 두었는지 찾아봅니다. 담아 둔 응답으로 바로 답하면 캐시 적중이라고 부릅니다. 없어서 오리진 서버까지 가야 하면 캐시 미스라고 부릅니다.
아래 그림은 두 사용자가 같은 상품 목록을 차례로 요청할 때 무슨 일이 일어나는지 보입니다.
sequenceDiagram
participant 가 as 사용자 가
participant 캐시 as 리버스 프록시 캐시
participant 오리진 as 오리진 서버
participant 나 as 사용자 나
가->>캐시: 상품 목록을 달라
Note over 캐시: 담아 둔 응답이 없다 · 캐시 미스
캐시->>오리진: 요청을 넘긴다
오리진-->>캐시: 응답
캐시-->>가: 응답 · 다음 요청에 쓰려고 담아 둔다
나->>캐시: 상품 목록을 달라
Note over 캐시: 담아 둔 응답이 있다 · 캐시 적중
캐시-->>나: 담아 둔 응답
두 번째 요청은 오리진 서버까지 가지 않았습니다. 전체 요청 가운데 적중으로 끝난 비율을 캐시 적중률이라고 부릅니다. 적중률이 높을수록 오리진 서버가 받는 요청이 줄어듭니다.
같은 요청을 가려내는 기준
캐시는 새로 온 요청이 담아 둔 응답과 짝이 맞는지 가려야 합니다. 그 기준을 캐시 키라고 부릅니다. 대개 요청의 메서드와 호스트 이름과 경로와 쿼리 문자열을 이어 붙여 만듭니다.
메서드는 요청이 무엇을 하려는지 적는 낱말입니다. 읽기에는 GET 을, 쓰기에는 POST 를 흔히
씁니다. GET 으로 shop.example.com/products?page=2 를 요청하면 메서드와 호스트 이름과 경로와
쿼리 문자열 넷이 합쳐 키 하나가 됩니다. page=3 을 요청하면 쿼리 문자열이 달라 다른 키가
됩니다.
주소가 같은데 응답이 갈리는 경우도 있습니다. 같은 주소가 요청한 언어에 따라 한국어 화면과 영어
화면을 따로 내주면 그렇습니다. 브라우저는 원하는 언어를 Accept-Language 요청 헤더에 적어
보냅니다.
이때 오리진 서버는 응답에 Vary: Accept-Language 처럼 Vary 헤더를 실어 이 요청 헤더가
응답을 가른다고 알립니다. 캐시는 그 요청 헤더의 값까지 키에 넣습니다.
담을지와 수명을 정하는 헤더
오리진 서버는 응답마다 담아도 되는지와 얼마 동안 써도 되는지를 Cache-Control 응답 헤더로 알립니다. 이 헤더의 값에는 지시어를 쉼표로 늘어놓습니다. 지시어는 캐시에게 할 일을 이르는 낱말 하나하나입니다.
리버스 프록시 캐시는 여러 사용자가 함께 쓰는 공유 캐시입니다. 그래서 한 사람 것이라고 표시된 응답은 담지 않습니다. 공유 캐시에만 따로 걸리는 지시어도 있습니다.
| 지시어 | 리버스 프록시 캐시가 하는 일 |
|---|---|
max-age=60 |
60초 동안 오리진 서버에 묻지 않고 내준다. 브라우저 캐시에도 걸린다 |
s-maxage=600 |
공유 캐시에만 걸린다. max-age 대신 이 값을 써서 600초 동안 내준다 |
private |
담지 않는다. 한 사람 것이라는 표시다 |
no-store |
담지 않는다. 어느 캐시도 담으면 안 된다 |
no-cache |
담아 두되 내주기 전에 매번 오리진 서버에 확인한다 |
max-age 와 s-maxage 를 함께 쓰면 캐시마다 수명을 나눌 수 있습니다. 브라우저에는 60초만
담아 두게 하고 리버스 프록시 캐시에는 10분을 담아 두게 하는 식입니다. 리버스 프록시 캐시는
뒤에서 볼 퍼지로 운영자가 언제든 비울 수 있으므로 길게 잡아도 부담이 덜합니다.
운영자가 쥔 캐시라서 되는 일
리버스 프록시 캐시는 서비스 운영자의 설비 안에 있습니다. 브라우저 캐시와 달리 운영자가 설정을 고치고 명령을 내릴 수 있습니다. 이 점이 두 가지를 가능하게 합니다.
첫째는 담는 규칙을 운영자가 캐시 설정으로 정하는 것입니다. 오리진 서버가 Cache-Control 을 안
붙이는 응답도 설정으로 몇 분씩 담게 할 수 있습니다. 애플리케이션 코드를 고치지 않고 캐시를 붙일
수 있는 까닭입니다.
둘째는 담아 둔 응답을 원할 때 지우는 것입니다. 무효화는 담긴 응답을 더는 못 쓰게 만드는 일을 통틀어 이르는 말입니다.
퍼지는 무효화 가운데 운영자가 명령으로 특정 주소의 응답을 지우는 일입니다. 글을 고친 직후 그 주소만 퍼지하면 다음 요청부터 새 글이 나갑니다. 사용자 브라우저에 이미 들어간 응답은 서버가 이렇게 지울 수 없습니다.
신선한 응답과 낡은 응답
담아 둔 응답을 오리진 서버에 다시 묻지 않고 써도 되는지를 신선도라고 부릅니다. 써도 되는 동안의 응답은 신선한 상태입니다. 수명을 넘기면 낡은 상태가 됩니다.
낡은 응답을 바로 버리지는 않습니다. 캐시는 오리진 서버에 이 응답이 아직 맞는지 묻습니다. 이 확인을 재검증이라고 부릅니다.
재검증에는 응답의 버전을 가리키는 값이 쓰입니다. 오리진 서버가 응답에 붙여 보낸 ETag(entity tag, 엔티티 태그)가 대표입니다. 응답 내용이 바뀌면 ETag 값도 바뀝니다.
캐시는 담아 둔 응답의 ETag 값을 If-None-Match 요청 헤더에 실어 오리진 서버에 묻습니다. 이
값의 응답이 아직 맞느냐는 물음입니다.
내용이 안 바뀌었으면 오리진 서버는 본문 없이 304 Not Modified 로만 답합니다. 캐시는 담아 둔
응답을 다시 신선한 것으로 돌려 씁니다. 내용이 바뀌었으면 오리진 서버가 새 응답을 보내고 캐시는
그것으로 바꿔 담습니다.
아래 그림은 담긴 응답 하나가 거치는 상태를 보입니다.
stateDiagram-v2
[*] --> 신선: 응답을 담는다
신선 --> 낡음: 수명이 다한다
낡음 --> 신선: 재검증 · 바뀐 것이 없다
낡음 --> 신선: 재검증 · 새 응답으로 바꿔 담는다
신선 --> [*]: 퍼지
낡음 --> [*]: 퍼지
내용이 안 바뀐 재검증은 본문을 다시 받지 않으므로 캐시 미스보다 가볍습니다. 오리진 서버는 화면을 새로 만드는 대신 ETag 값만 견줘 보면 됩니다.
얻는 것
리버스 프록시 캐시가 주는 이득은 셋입니다. 셋 다 오리진 서버가 할 일을 캐시가 덜어 가는 데서 나옵니다.
| 얻는 것 | 어떻게 얻나 |
|---|---|
| 오리진 서버의 부하가 준다 | 같은 응답을 한 번만 만들고 나머지는 캐시가 답한다 |
| 응답이 빨라진다 | 데이터베이스 조회와 화면 조립을 건너뛰고 담아 둔 응답을 바로 보낸다 |
| 오리진 서버가 멈춰도 버틴다 | 오리진 서버가 허락해 둔 응답은 오리진이 오류를 낼 때 낡은 채로라도 내준다 |
셋째 줄의 허락에는 stale-if-error 지시어가 쓰입니다. 오리진 서버가 Cache-Control 헤더에
이 지시어를 실어 둡니다.
수명을 짧게 잡아도 효과가 납니다. 수명을 1초로 잡았다고 해 봅니다. 수명이 끝난 뒤 첫 요청이 새 응답을 받아 오는 동안 다른 요청이 끼어들지 않으면, 1초에 천 번 오는 같은 요청 가운데 오리진 서버에 가는 것은 한 번입니다. 그 사이에 끼어든 요청까지 미스가 되는 경우는 다음 절의 셋째 문제에서 봅니다.
쓰면 생기는 문제
첫째는 낡은 내용입니다. 오리진 서버에서 가격을 고쳐도 캐시에 든 옛 응답이 수명을 다 채울 때까지 나갑니다. 퍼지를 빠뜨리면 사용자는 고치기 전 가격을 봅니다. 이런 상태의 값을 낡은 데이터라고 부릅니다.
둘째는 남의 응답이 새는 것입니다. 쿠키는 서버가 브라우저에 맡겨 두는 작은 값입니다. 로그인한 사람을 알아보는 데 씁니다. 로그인 여부에 따라 화면이 달라지는 주소가 있습니다. 캐시 키가 쿠키를 안 보면 그 주소에서 한 사람의 화면이 다음 사람에게 나갑니다.
새 쿠키를 내려보내는 응답도 같은 위험을 안습니다. 서버는 Set-Cookie 응답 헤더로 쿠키를
내려보냅니다. 이 헤더가 붙은 응답을 담으면 한 사람의 쿠키가 다음 방문자에게 건네집니다.
셋째는 한꺼번에 몰리는 미스입니다. 인기 응답의 수명이 끝나면 첫 요청이 새 응답을 받아 오는 동안 들어온 요청들도 전부 미스가 됩니다. 그 요청들이 한꺼번에 오리진 서버로 몰려갑니다. 이 현상을 캐시 스탬피드라고 부릅니다.
CDN 과 이어지는 관계
CDN(Content Delivery Network, 콘텐츠 전송 네트워크)은 리버스 프록시 캐시를 세계 여러 도시에 깔아 둔 것으로 볼 수 있습니다. 사용자는 가장 가까운 도시의 캐시에 닿습니다. 오리진 서버의 부하를 덜어 주는 일은 같습니다.
CDN 에는 사용자와의 거리를 줄이는 역할이 더 있습니다. 먼 나라의 사용자도 가까운 도시에서 응답을 받습니다. 네트워크를 오가는 시간이 그만큼 줄어듭니다.
둘을 겹쳐 쓰기도 합니다. CDN 뒤, 오리진 서버 앞에 리버스 프록시 캐시를 하나 더 두는 구성입니다. CDN 에서 미스가 난 요청이 오리진 서버에 닿기 전에 한 번 더 걸러집니다.
잘 맞는 응답과 안 맞는 응답
리버스 프록시 캐시가 효과를 내는 조건은 둘입니다. 같은 응답을 여러 사람이 찾아야 합니다. 그 응답이 잠깐 낡아도 괜찮아야 합니다.
| 응답 | 맞나 | 까닭 |
|---|---|---|
| 이미지 · 스타일 파일 · 스크립트 파일 | 잘 맞는다 | 누가 요청해도 같고 잘 안 바뀐다 |
| 공개된 글 · 상품 목록 | 맞는다 | 여럿이 같은 것을 찾는다. 몇 초에서 몇 분 낡아도 탈이 적다 |
| 로그인한 사람의 화면 · 장바구니 | 안 맞는다 | 사람마다 달라서 돌려 쓸 수 없다 |
| 쓰기 요청의 응답 | 안 맞는다 | 서버의 상태를 바꾸는 요청이라 매번 오리진 서버가 처리해야 한다 |
| 초 단위로 바뀌는 재고 · 시세 | 안 맞는다 | 낡은 값이 곧 틀린 값이다 |
첫째 조건이 빠지면 담아 두는 비용만 듭니다. 같은 응답을 다시 찾는 사람이 없어 적중이 안
납니다. 둘째 조건이 빠지면 적중할수록 틀린 값이 나갑니다. 사람마다 다른 응답은 private 을
붙여 공유 캐시가 담지 않게 합니다.
관련 항목
리버스 프록시 캐시가 속하는 상위 분류
캐싱 · HTTP 캐시 · 공유 캐시 · 웹 캐시 · 리버스 프록시
요청 경로 위에서 앞뒤로 서는 다른 캐시
브라우저 캐시 · 사설 캐시 · 프록시 캐시 · CDN · 엣지 서버 · 오리진 실드
리버스 프록시 캐시 뒤에서 응답을 만드는 서버
오리진 서버 · 애플리케이션 서버 · 웹 서버 · 업스트림 · 백엔드
리버스 프록시 캐시가 담을지와 수명을 읽는 헤더·지시어
Cache-Control · s-maxage · max-age · no-store · no-cache · Expires · Age · Set-Cookie
리버스 프록시 캐시가 요청과 응답을 맞춰 보는 기준
캐시 키 · Vary · URL · 쿼리 문자열 · HTTP 메서드 · 쿠키
담아 둔 응답을 언제까지 쓸지 정하는 개념
신선도 · 재검증 · 조건부 요청 · ETag · 304 Not Modified · TTL · stale-while-revalidate · stale-if-error
담아 둔 응답을 지우거나 밀어내는 수단
리버스 프록시 캐시의 성과를 가르는 결과와 지표
캐시 적중 · 캐시 미스 · 캐시 적중률 · 응답 시간 · 지연 시간
리버스 프록시 캐시에서 터지는 문제
캐시 스탬피드 · 낡은 데이터 · 캐시 포이즈닝 · 웹 캐시 기만 · 썬더링 허드
리버스 프록시 캐시를 구현한 소프트웨어
nginx · Varnish · Squid · Apache Traffic Server
리버스 프록시 캐시를 정의하는 표준·문서
다른 이름: reverse proxy cache · 게이트웨이 캐시 · gateway cache