콘텐츠 전송 네트워크(CDN), 무엇을 얼마나 오래 캐시할까
CDN은 방문자와 가까운 서버에 응답의 사본을 두어 거리에서 오는 지연을 줄인다.
사람마다 다른 응답이 남에게 보이는 사고와 고친 페이지가 늦게 반영되는 현상도 이 사본을 다루는 방식에서 나온다.
이 글의 목차서버가 멀면 왜 느릴까6
페이지를 고쳐 배포했는데 옛 화면이 계속 보일 때가 있다.
사이트 앞에 CloudFront 같은 CDN을 두었다면 흔히 겪는 일이다.
CloudFront는 AWS의 CDN 서비스다.
CloudFront 설정 화면에는 원본, 캐시 정책, TTL, 무효화 같은 낯선 말이 줄줄이 나온다.
이 칸들이 무슨 뜻인지, 옛 화면이 왜 계속 보이는지는 CDN이 동작하는 방식을 알면 함께 풀린다.
서버가 멀면 왜 느릴까
브라우저가 웹 페이지를 받으려면 요청을 보내고 응답을 받아야 한다.
이 왕복에 걸리는 시간을 지연(latency)이라고 한다.
지연의 상당 부분은 거리에서 온다.
신호가 아무리 빨라도 대륙을 건너 왕복하면 눈에 띄는 시간이 걸린다.
서버를 더 좋은 것으로 바꿔도 이 시간은 줄지 않는다.
게다가 왕복은 한 번으로 끝나지 않는다.
연결을 맺을 때, HTTPS라면 암호화 방식을 협상할 때도 왕복이 오간다.
거리에서 오는 지연이 그만큼 여러 번 쌓인다.
그래서 응답을 방문자 가까이에 두는 방법이 나왔다.
원래 응답을 만드는 서버를 원본(origin)이라고 하고, 응답의 사본을 보관했다가 다시 내주는 일을 캐시라고 한다.
CDN(Content Delivery Network)은 여러 지역에 캐시 서버를 두고, 요청을 방문자에게 알맞은 서버로 보내는 구조다.
방문자 가까이에서 응답하는 이 서버들을 엣지(edge)라고 부른다.
엣지에 사본이 있으면 바로 내주고, 없으면 원본에서 받아 온 뒤 보관한다.
앞의 경우를 캐시 적중(hit), 뒤의 경우를 캐시 미스(miss)라고 한다.
적중이든 미스든 방문자가 연결을 맺고 암호화를 협상하는 상대는 가까운 엣지다.
먼 원본까지 다녀오는 일은 미스일 때 엣지가 맡는다.
웹이 자주 멈추던 시절에 나온 구조
1990년대 후반의 웹은 인기 있는 사이트에 사람이 몰리면 화면이 좀처럼 뜨지 않았다.
MIT의 수학자 Tom Leighton과 대학원생 Danny Lewin은 콘텐츠를 여러 서버에 복제해 두고 알맞은 곳에서 내주는 방법을 만들어, 당시 ‘월드 와이드 웨이트’라고 불리던 기다림을 풀었다.
두 사람은 1998년 Akamai를 세웠고, 1999년 4월 상용 서비스를 시작했다.
지금은 여러 회사가 CDN을 제공하며, CloudFront도 그중 하나다.
같은 주소면 같은 응답일까
사본을 두는 순간 새 문제가 생긴다.
엣지는 어떤 요청에 이미 가진 사본을 내줘도 되는지 판단해야 한다.
이 판단에 쓰는 기준을 캐시 키라고 하며, 기본적으로는 주소가 같으면 같은 응답으로 본다.
그런데 같은 주소라도 로그인한 사람에 따라 이름과 주문 내역이 달라지는 페이지가 있다.
이런 페이지를 주소만 보고 내주면 사고가 난다.
HTTP 캐시 규격은 여러 사람이 함께 쓰는 공유 캐시와 한 사람만 쓰는 개인 캐시를 구별한다.
CDN의 엣지는 공유 캐시이고, 브라우저 안의 캐시는 개인 캐시다.
응답에는 Cache-Control이라는 헤더로 캐시 지시를 붙인다.
헤더는 응답 본문과 함께 오가는 부가 정보다.
private은 공유 캐시에 저장하지 말라는 뜻이고, no-store는 어디에도 저장하지 말라는 더 강한 지시다.
사람마다 다른 응답을 공유해도 되는 응답으로 잘못 설정하면 다른 사람의 화면이 그대로 전달될 수 있다.
빈 캐시에서 A와 B가 차례로 자기 화면을 요청할 때, 설정에 따라 B가 받는 화면이 달라진다.
방문자 A가 받은 화면
A의 화면원본에서 받음
방문자 B가 받은 화면
A의 화면잘못 공유한 사본
방문자 A가 받은 화면
A의 화면공유 캐시에 저장 안 함
방문자 B가 받은 화면
B의 화면원본이 B에게 응답
사람마다 다른 화면을 같은 주소라는 이유로 공유하면 다른 사람의 화면이 갈 수 있다. private는 공유 캐시에 저장하지 말라는 지시다.
B에게 A의 화면이 갔다. 개인 응답을 주소만으로 공유하도록 잘못 설정했기 때문이다.
개인 응답을 일부러 잘못 공유한 가상의 비교다. 각 설정은 빈 캐시에서 시작하며 특정 CDN의 기본 동작을 뜻하지 않는다.
캐시 키에 로그인 정보를 덧붙여 문제를 피하려 하기보다, 이 응답을 공유해도 되는지부터 정하는 편이 안전하다.
페이지를 고쳤는데 옛 화면이 보인다
엣지가 사본을 언제까지 보관할지는 원본이 응답에 적어 보낸다.
Cache-Control의 max-age가 그 값이고 초 단위로 적는다.
이렇게 정한 보관 기간을 흔히 TTL(Time to Live)이라고 부른다.
원본이 보관 기간을 적어 보내지 않으면 CDN의 기본값을 쓴다.
CloudFront는 이런 값을 캐시 정책이라는 설정에 모아 두는데, 캐시 정책을 쓰지 않으면 기본 TTL이 24시간이다.
그러면 원본의 페이지를 고쳐도 엣지에 남은 옛 사본이 그 시간 동안 나갈 수 있다.
옛 사본을 치우는 방법은 두 가지다.
하나는 엣지에 사본을 지우라고 알리는 무효화(invalidation)이고, 다른 하나는 내용이 바뀔 때 파일 이름을 바꾸는 방법이다.
AWS는 파일을 자주 바꾼다면 무효화보다 이름에 버전을 붙이는 방식을 주로 쓰라고 권한다.
무효화는 CloudFront의 사본만 지우기 때문에 브라우저나 회사 프록시에 남은 사본은 만료될 때까지 그대로 보일 수 있다.
이미지, 스크립트, 스타일 파일에는 이름을 바꾸는 방법이 잘 맞는다.
빌드 도구가 파일 내용으로 계산한 짧은 문자열을 파일 이름에 넣는데, 이것을 지문(fingerprint)이라고 한다.
내용이 바뀌면 지문이 바뀌고 주소도 바뀐다.
새 페이지는 새 주소의 파일을 요청하니 옛 사본과 부딪히지 않는다.
그래서 지문이 붙은 파일은 아주 길게 보관해도 괜찮다.
남는 것은 HTML 문서다.
방문자가 가장 먼저 받는 입구 문서라서 주소를 바꿀 수 없다.
HTML은 짧게 보관하거나, 쓸 때마다 원본에 확인하게 한다.
HTML만 새로 받으면 그 안에 적힌 새 파일 이름을 따라 나머지 파일도 새것으로 바뀐다.
| 파일의 성격 | 보관 방식 | 잘못 정하면 |
|---|---|---|
| 이름에 지문이 붙은 파일 | 아주 길게 | 짧게 두면 방문자가 같은 파일을 계속 다시 받는다 |
| 주소가 고정된 HTML | 짧게 두거나 매번 확인 | 길게 두면 새 배포가 늦게 반영된다 |
| 사람마다 다른 응답 | 공유 캐시에 두지 않음 | 남의 화면이 다른 사람에게 간다 |
no-cache와 no-store는 뜻이 다르다
이름만 보면 no-cache는 캐시하지 말라는 뜻 같다.
하지만 HTTP 캐시 규격에서 no-cache는 저장한 사본을 쓰기 전에 원본에 확인을 받으라는 뜻이다.
확인 요청에 원본이 바뀌지 않았다고 답하면(304 Not Modified) 본문은 다시 오지 않고, 캐시는 저장해 둔 사본으로 응답한다.
저장 자체를 막는 것은 no-store다.
HTML처럼 늘 최신이어야 하지만 공유해도 되는 응답에는 no-cache를, 개인 정보가 담겨 저장하면 안 되는 응답에는 no-store를 쓴다.
배포할 때는 옛 파일을 바로 지우지 않는다
지문을 붙였다고 옛 파일을 배포 직후에 지우면 문제가 생긴다.
이미 페이지를 열어 둔 방문자는 옛 HTML을 들고 있어서 옛 이름의 파일을 계속 요청할 수 있다.
그래서 새 파일을 먼저 올리고, 옛 파일은 한동안 남겨 둔다.
로그인한 사용자만 보는 경로는 아예 캐시하지 않게 따로 떼어 두는 경우가 많다.
CloudFront에서는 그런 경로에 캐시를 끄는 관리형 정책 CachingDisabled를 붙인다.
동적인 콘텐츠와 캐시할 수 없는 요청을 위한 정책이다.