3.15 캐싱과 콘텐츠 전송
개요와 동기
캐시는 원본보다 더 빠르거나 더 가까운 곳에 보관한 데이터의 사본으로, 완전하고 비싼 작업을 다시 하지 않고 요청에 답할 수 있게 합니다. 빠르게 느껴지는 거의 모든 시스템은 캐싱 덕분에 빠릅니다. 40밀리초 걸릴 데이터베이스 질의는 결과가 이미 메모리에 있으면 1밀리초 안에 돌아옵니다. 대양을 건널 이미지는 같은 도시의 기계에서 제공됩니다. 캐싱은 가진 것 중 지렛대 효과가 가장 큰 성능 기법이며, 미묘하고 미칠 것 같은 버그를 안길 가능성이 가장 높은 기법이기도 합니다.
이 장은 캐싱 전략을 깊이 다룹니다. 3.4장(데이터 아키텍처와 저장소)은 캐시와 콘텐츠 전송 네트워크를 여러 저장소 관심사 중 하나로 소개하고, 3.13장(네트워킹과 연결성)은 그것들이 타는 네트워크 경로를 다룹니다. 여기서는 결정을 얻습니다. 캐시를 어디에 둘지, 어떻게 키를 지정할지, 언제 무효화할지, 부하 아래서 어떻게 보호할지, 속도와 맞바꾸는 오래됨을 어떻게 추론할지입니다. 캐싱은 성능 엔지니어링(2.16장), 확장성과 복원력(3.5장), 분산 시스템의 부분 실패 현실(3.3장), 그리고 오염된 캐시가 수천 사용자에게 공격을 제공할 수 있으므로 애플리케이션 보안(4.2장)에 닿습니다.
동기는 세 가지 지렛대로 요약됩니다. 캐싱은 지연을 줄여 사용자가 덜 기다립니다. 부하를 줄여 원본이 같은 하드웨어로 더 많은 트래픽을 처리합니다. 비용을 줄입니다. 엣지에서 답한 요청은 데이터베이스, 컴퓨트, 송신 청구서에 닿지 않기 때문입니다. 큰 팀에게 공유 캐싱 전략은 예측 가능하게 확장되는 플랫폼과 모든 서비스가 무효화를 다시 발명하고 틀리는 플랫폼의 차이입니다. 신고 마감일과 출시일에 트래픽이 급증하는 기업과 정부 시스템에서, 잘 설계된 캐시는 흔히 동작하는 포털과 공개적인 실패 사이에 서 있는 것입니다.
핵심 원칙
- 지연, 부하, 비용을 줄이려고 캐시하되, 어느 것을 사고 있는지 아십시오.
- 가장 도움이 되는 곳에 가까운, 캐시 계층의 알맞은 층에 캐시를 두십시오.
- 무효화를 어려운 부분으로 다루십시오. 캐시하기 전에 키와 수명을 설계하십시오.
- 병합, 지터, 스탬피드 방어로 부하 아래서 캐시를 보호하십시오.
- 쓰기 패턴을 의도적으로 고르십시오. 일관성과 속도는 서로 반대로 당깁니다.
- 적중률, 오래됨, 원본 부하를 측정하십시오. 측정되지 않는 캐시는 부채입니다.
- 캐시된 콘텐츠를 공격 표면으로 다루십시오. 오염된 캐시는 모두에게 제공합니다.
권장 사항
캐시 계층을 이해한다
캐싱은 한 곳의 한 가지가 아닙니다. 각각이 앞의 것보다 사용자에게 가까운 사본들의 계층이며, 그 모두에 걸쳐 설계합니다. 사용자에 가장 가까운 것이 클라이언트 캐시입니다. 브라우저의 HTTP 캐시, 모바일 앱의 로컬 저장소, 프로세스 내 메모리 캐시입니다. 다음은 콘텐츠 전송 네트워크(CDN), 곧 사용자 가까운 네트워크 엣지에 콘텐츠 사본을 보유한 전 세계에 분산된 서버 집단입니다. 그 뒤에는 서버 앞의 공유 캐시인 리버스 프록시나 게이트웨이 캐시가 있습니다. 그다음 애플리케이션 캐시, 곧 계산된 결과, 세션, 렌더링된 조각을 담은 인메모리 데이터 그리드 같은 빠른 키-값 저장소입니다. 마지막으로 디스크 접촉을 줄이려고 뜨거운 페이지를 메모리에 두는 데이터베이스 자체의 질의 및 버퍼 캐시입니다.
각 계층은 별개의 일을 합니다. 클라이언트 캐시는 요청을 아예 없애고, CDN은 전 세계 읽기 트래픽을 흡수하고, 리버스 프록시는 원본을 반복되는 동일한 작업으로부터 보호하고, 애플리케이션 캐시는 재계산을 아끼고, 데이터베이스 캐시는 저장소의 응답성을 유지합니다. 모든 계층을 놓치고 데이터베이스에 닿는 요청은 가진 것 중 가장 느리고 가장 비싼 경로이므로, 계층의 요점은 안전하게 가능한 한 위쪽과 바깥쪽에서 답하는 것입니다. 시스템으로 설계하십시오. 애플리케이션 계층의 캐시된 조각과 그 위의 오래된 CDN 사본은 사용자를 혼란스럽게 하는 방식으로 어긋날 수 있기 때문입니다.
무효화를 어려운 문제로 다룬다
컴퓨터 과학에서 가장 어려운 두 문제가 이름 짓기, 캐시 무효화, 하나 차이 오류라는 오래된 농담이 있습니다. 이 농담이 지속되는 이유는 무효화가 정말 어렵기 때문입니다. 캐시는 사본이고, 원본이 바뀌는 순간 모든 사본은 잠재적 거짓말입니다. 세 가지 넓은 전략이 있습니다. 항목이 오래된 것으로 간주되기 전까지 유효한 기간인 TTL(time to live)을 둔 시간 기반 만료가 가장 단순합니다. 한정된 오래됨을 받아들이고 항목이 낡아 나가게 둡니다. 명시적 무효화는 기반 데이터가 바뀔 때 항목을 비우거나 갱신하며, 정밀하지만 사본이 사는 모든 곳을 알아야 합니다. 이벤트 기반 무효화는 캐시가 변경 이벤트를 구독해 스스로 갱신하게 하며, 많은 캐시에 걸쳐 더 잘 확장되지만 메시징 의존성을 더합니다.
대부분의 실제 시스템은 이를 섞습니다. 자주 바뀌고 몇 초의 오래됨을 허용하는 데이터에는 짧은 TTL, 드물게 바뀌지만 바뀔 때 정확해야 하는 데이터에는 더 긴 TTL과 명시적 비우기, 일단 발행되면 불변인 콘텐츠에는 버전 관리되는 캐시 키입니다. 버전 키 요령은 내면화할 가치가 있습니다. 무효화하는 대신 키를 바꿉니다. app.v187.css로 제공되는 스타일시트는 비울 필요가 없습니다. 새 버전은 새 키이고 옛 것은 그저 요청되지 않게 되기 때문입니다. 무효화 문제를 이름 짓기 문제로 바꿀 수 있을 때마다 그렇게 하십시오.
캐시 키와 TTL을 의도적으로 설계한다
캐시는 키만큼만 좋습니다. 캐시 키는 값이 저장되고 조회되는 식별자이며, 잘못 잡으면 정반대의 두 실패가 생깁니다. 너무 굵으면 한 사용자의 데이터를 다른 사용자에게 제공합니다. 사용자 신원을 무시하는 URL로 캐시된 개인화 페이지는 데이터 유출입니다. 너무 잘으면 두 요청이 키를 공유하지 않아 적중률이 무너집니다. 무엇이 키에 속하는지 의도적으로 결정하십시오. 자원 신원에 응답을 정당하게 달리하는 것(언어, 통화, 기기 등급)을 더하고 그렇지 않은 것은 넣지 마십시오. 쿼리 매개변수 순서 같은 사소한 차이가 캐시를 파편화하지 않도록 키를 정규화하십시오.
TTL도 같은 생각이 필요합니다. TTL은 제공할 최대 오래됨에 대한 약속이므로, 누군가 짐작한 둥근 숫자가 아니라 데이터의 실제 허용도로 정하십시오. 주식 시세는 몇 초, 상품 카탈로그는 몇 분, 공표된 규정은 몇 시간이거나 버전 키와 만료 없음입니다. 함께 쓰인 항목 묶음이 같은 순간에 모두 만료되어 원본에 스탬피드를 일으키지 않도록 지터라는 작은 무작위 퍼짐을 더하십시오. 이런 선택을 적어 두십시오. 근거 없는 TTL은 다음 엔지니어가 바꾸기 두려워할 숫자이기 때문입니다.
스탬피드로부터 보호하고 요청을 병합한다
인기 있는 캐시 항목이 만료되면 그것을 원했던 모든 요청이 한꺼번에 놓치고 함께 원본으로 몰립니다. 이것이 천둥 떼라고도 불리는 캐시 스탬피드이며, 캐시가 보호하던 바로 그 데이터베이스를 쓰러뜨릴 수 있습니다. 방어를 한 번 만들어 어디서나 재사용하십시오. 요청 병합(single-flight)은 없는 키의 첫 요청만 값을 재계산하게 하고 나머지는 그 결과를 기다리게 해, 천 개의 동시 미스가 원본 호출 하나를 일으킵니다. 확률적 조기 재계산은 뜨거운 항목을 만료 직전에 무작위로 갱신해, 군중이 미스를 보기 전에 하나의 백그라운드 요청이 그것을 갱신하게 합니다. stale-while-revalidate 정책은 약간 오래된 사본을 즉시 제공하고 비동기로 갱신해, 사용자가 미스를 기다리는 일이 아예 없게 합니다.
이런 패턴은 캐시가 가장 필요한 바로 그때, 곧 피크 부하 아래서 가장 중요하므로 현실적인 규모에서 검증하십시오. 사용자 열 명에서 동작하는 방어도 만 명에서는 실패할 수 있습니다. 원본이 정말 느릴 때 캐시 계층이 쌓이는 대신 그것을 보호하도록 3.5장의 복원력 패턴, 특히 타임아웃과 서킷 브레이커와 짝지으십시오. 목표는 급증을 장애로 증폭하는 캐시가 아니라 압박 아래서 가장 잘 동작하는 캐시입니다.
쓰기 패턴을 의도적으로 고른다
쓰기를 다루는 방식이 캐시가 얼마나 신선하게 유지되고 실패 시 무엇을 위험에 빠뜨리는지를 결정합니다. 네 가지 흔한 패턴이 있습니다. cache-aside(지연 로딩)에서 애플리케이션은 캐시를 확인하고, 미스에서 원본을 읽어 캐시를 채우고 값을 돌려줍니다. 쓰기는 원본으로 가고 항목을 무효화합니다. 단순하고 캐시가 요청된 것만 담으므로 충분한 이유로 기본입니다. write-through에서는 모든 쓰기가 캐시와 원본에 함께 가서 캐시가 항상 최신이지만, 쓰기 지연과 읽히지 않을 수 있는 데이터를 캐시하는 대가를 치릅니다. write-back(write-behind)에서는 쓰기가 캐시에 먼저 닿고 원본으로 비동기로 플러시되어 쓰기가 빠르지만, 플러시 전에 캐시가 죽으면 손실 위험이 있습니다. write-around에서는 쓰기가 캐시를 건너뛰고 곧바로 원본으로 가서, 거의 읽히지 않는 쓰기 위주 데이터로 인한 교란을 피하는 대신 첫 읽기 미스가 보장됩니다.
시스템 전체에 한 번이 아니라 워크로드마다 고르십시오. 읽기 위주 카탈로그는 cache-aside나 write-through에 맞습니다. 쓰기 위주 로그나 지표 스트림은 write-around에 맞아, 아무도 다시 읽지 않는 데이터로 캐시가 교란되지 않습니다. write-back은 작고 이해된 손실 위험이 허용되고 내구성이 다른 곳에서 처리되는 고처리량 쓰기에 맞습니다. 각 캐시의 패턴을 명시적으로 밝히십시오. 코드는 write-back을 하는데 cache-aside라고 가정하는 독자는 신선도와 실패 동작을 모두 잘못 판단하기 때문입니다.
접근 패턴에 맞는 축출 정책을 고른다
캐시는 크기가 고정되어 있으므로, 가득 차면 무언가 나가야 합니다. 축출 정책이 무엇인지 결정합니다. 최근 최소 사용(LRU)은 가장 오래 건드리지 않은 항목을 축출하며, 최근 사용이 미래 사용을 예측한다는 데 걸고, 합리적인 기본입니다. 최소 빈도 사용(LFU)은 적중이 가장 적은 항목을 축출하며, 몇몇 항목이 영원히 인기 있는 안정적인 핫 집합에 맞지만 한때 뜨거웠던 항목에 매달리고 적응하지 못할 수 있습니다. 세그먼트 LRU와 적응형 정책 같은 변형은 최근성과 빈도를 섞습니다. 선입선출과 단순한 시간 기반 만료는 더 싸지만 더 무딥니다.
데이터가 접근되는 방식에 정책을 맞추십시오. 거의 바뀌지 않는 작은 핫 집합에는 LFU나 빈도 인식 정책, 뉴스나 트렌딩 콘텐츠처럼 인기가 시간에 따라 움직이는 곳에는 LRU입니다. 무엇을 고르든 핫 집합이 들어가도록 캐시 크기를 정하십시오. 작업 집합을 담기에 너무 작은 캐시는 항목이 다시 필요하기 직전에 축출하며 스래싱합니다. 축출 비율을 일급 지표로 지켜보십시오. 갑작스러운 상승은 대개 캐시가 작거나 키 폭발이 파편화하고 있다는 뜻입니다.
HTTP 캐싱 의미론을 올바르게 쓴다
웹은 HTTP에 내장된 성숙하고 표준화된 캐싱 모델을 갖고 있으며, 잘 쓰면 클라이언트와 CDN 캐싱을 공짜로 얻습니다. Cache-Control 헤더가 제어 표면입니다. max-age는 신선도 수명을 정하고, public과 private은 공유 캐시가 응답을 저장해도 되는지 말하고, no-store는 캐싱을 금지하고, stale-while-revalidate는 갱신하는 동안 오래된 사본을 제공하는 것을 허용합니다. 검증은 캐시가 본문을 다시 가져오지 않고 싸게 신선도를 확인하게 합니다. ETag(엔티티 태그)는 서버가 응답에 붙이는 불투명한 버전 식별자입니다. 클라이언트가 If-None-Match 헤더로 그것을 돌려보내고, 아무것도 바뀌지 않았으면 서버는 본문 없이 304 Not Modified로 응답합니다. Last-Modified와 If-Modified-Since는 타임스탬프로 같은 일을 합니다.
실용적 규율은 명시적이 되는 것입니다. 캐시가 휴리스틱으로 짐작하게 두지 말고 모든 응답에 Cache-Control을 설정하십시오. 공유 프록시가 결코 저장하지 않도록 사용자별 비공개 응답은 private이나 no-store로 표시하십시오. 흔하고 위험한 실수입니다. 정적 자산에는 긴 max-age와 immutable 지시어를 가진 버전 URL을, 예측할 수 없이 바뀌는 콘텐츠에는 ETag 검증을 쓰십시오. 이 헤더를 맞게 하면 클라이언트와 CDN 계층 전체가 직접 만들 필요 없는 올바르고 표준 기반의 캐시가 됩니다.
CDN과 엣지 컴퓨팅으로 작업을 엣지로 밀어낸다
CDN은 사용자 가까이에 정적 파일을 캐시하는 방법으로 시작했고, 여전히 그것을 훌륭하게 합니다. 이미지, 스크립트, 비디오, 다운로드를 먼 원본이 아닌 몇 밀리초 거리의 엣지 위치에서 제공합니다. 현대적 CDN은 더 나아갑니다. 세밀한 키로 동적이고 개인화된 콘텐츠를 캐시하고, 엣지에서 TLS를 종단하고, 트래픽 급증과 분산 서비스 거부 공격을 흡수하며, 점점 더 여러분의 코드를 실행합니다. 엣지 컴퓨팅은 엣지 위치 자체에서 로직을 실행하므로, 중앙 리전으로의 왕복 없이 응답을 개인화하거나, 인가를 확인하거나, 페이지 조각을 조립할 수 있습니다.
대부분의 시스템을 지배하는 읽기에 이를 활용하십시오. 정적 자산을 오래 지속되는 버전 URL과 함께 CDN 뒤에 두고, 신선도가 허용하는 곳에서 엣지에 API 응답을 캐시하고(개인화가 유출되지 않게 신중히 키 지정), 지연에 민감하고 가벼운 로직에는 사용자 가까이의 엣지 컴퓨트를 쓰십시오. 트레이드오프는 도달 범위 대 통제입니다. 엣지는 빠르고 가깝지만 데이터에서 멀고 디버깅하기 더 어려우므로, 강한 일관성이나 신선한 권위 상태가 필요한 것은 원본에 두고 엣지가 방대한 캐시 가능한 읽기 트래픽을 처리하게 하십시오.
캐시를 공격 표면으로 다룬다
캐시는 같은 저장된 응답을 많은 사용자에게 제공하므로 표적이 됩니다. 캐시 오염은 캐시가 해로운 또는 공격자가 통제하는 응답을 저장하고 이어서 오는 모두에게 제공하도록 요청을 조작하는 공격입니다. 대개 키에 포함되지 않은 입력, 곧 애플리케이션이 응답에 반영하지만 캐시는 키를 만들 때 무시하는 헤더를 악용합니다. 관련된 웹 캐시 기만 공격은 피해자의 비공개 응답을 공개 URL 아래 저장하도록 캐시를 속입니다. 둘 다 키 지정과 입력 신뢰의 실패이며, 4.2장에서 더 넓게 다룹니다.
의도적으로 방어하십시오. 응답을 바꿀 수 있는 모든 입력을 캐시 키에 포함하고, 키에 없는 헤더를 캐시된 본문에 반영하기를 거부하십시오. 공유 캐시가 인증된 사용자별 응답을 공유 키로 저장하게 두지 마십시오. 캐시하기 전에 요청 경로와 매개변수를 정규화하고 검증하십시오. 콘텐츠 인코딩이나 언어처럼 실제로 중요한 헤더로 캐시가 응답을 나누도록 Vary를 올바르게 설정하십시오. 오염된 항목 하나가 모든 다운스트림 사용자를 해치므로, 캐시 설정을 보안에 민감한 코드로 다루고 그렇게 리뷰하십시오.
캐시 동작을 관측 가능하게 만든다
볼 수 없는 캐시는 관리할 수 없습니다. 대표 지표는 적중률, 곧 원본이 아니라 캐시에서 제공된 요청의 비율입니다. 95퍼센트에서 70퍼센트로 조용히 떨어지는 적중률은 원본 부하를 몇 배로 늘리고 장애에 앞설 수 있으며, 지켜볼 때만 일찍 잡습니다. 건강한 CDN 적중률이 그 아래에서 무너지는 애플리케이션 캐시 적중률을 가릴 수 있으므로 각 계층을 따로 계측하십시오. 이것이 9.2장의 관측 가능성 실천의 캐싱 전용 얼굴입니다.
적중 이상을 추적하십시오. 과소 크기를 잡는 축출 비율과 메모리 압박, 캐시가 실제로 더 빠른지 확인하는 계층별 지연, 캐시가 얼마나 많은 부하를 흡수하는지 보는 원본 요청 비율, 신선도 약속을 지키는지 확인하는 오래됨(제공된 항목이 얼마나 오래되었는가)입니다. 문제를 예측하는 비율, 특히 떨어지는 적중률이나 오르는 축출 비율에 경보를 걸어, 저하되는 캐시를 사용자가 아닌 대시보드에서 알게 하십시오. 관측되는 캐시는 조정할 수 있는 자산이고, 관측되지 않는 캐시는 놀라게 하려고 기다리는 숨은 의존성입니다.
장단점
캐싱은 신선도와 복잡성이라는 통화로 속도와 규모를 삽니다. 모든 캐시는 이 특정 데이터에 대해 오래되었지만 빠른 것이 신선하지만 느린 것을 이긴다는 내기이며, 기술은 그 내기를 기본이 아니라 의식적으로 거는 것입니다. 아래 표는 주요 선택을 요약합니다.
| 선택 | 장점 | 단점 |
|---|---|---|
| Cache-aside | 단순. 읽은 것만 캐시 | 첫 읽기는 항상 미스. 쓰기 후 잠깐의 오래됨 위험 |
| Write-through | 쓰기 시 캐시가 항상 최신 | 느린 쓰기. 읽히지 않을 수 있는 데이터를 캐시 |
| Write-back | 매우 빠른 쓰기. 급증을 흡수 | 플러시 전에 캐시가 실패하면 데이터 손실 위험 |
| Write-around | 쓰기 위주 데이터로 캐시가 교란되는 것을 피함 | 첫 읽기 미스 보장 |
| 짧은 TTL | 한정되고 작은 오래됨 | 낮은 적중률. 더 많은 원본 부하 |
| 긴 TTL / 버전 키 | 높은 적중률. 낮은 원본 부하 | 무효화하지 않으면 오래됨. 규율 있는 키 필요 |
| CDN과 엣지 | 전 세계 낮은 지연. 급증 흡수 | 데이터에서 멂. 디버깅과 무효화가 더 어려움 |
| LRU 축출 | 변하는 인기에 적응 | 스캔이 많은 부하에서 안정적인 핫 집합을 축출할 수 있음 |
| LFU 축출 | 안정적인 핫 집합 보호 | 적응이 느림. 한때 뜨거웠던 항목에 매달림 |
되풀이되는 긴장은 일관성 대 성능입니다. 긴 TTL과 높은 적중률을 가진 캐시는 빠르고 싸지만 오래된 데이터를 제공할 수 있고, 짧은 TTL과 공격적 무효화를 가진 캐시는 신선하고 정확하지만 원본을 더 일하게 합니다. 보편적으로 옳은 답은 없고, 데이터의 오래됨에 대한 실제 허용도로 정해지는 데이터별 옳은 답만 있습니다. 두 번째 긴장은 단순함 대 도달 범위입니다. 애플리케이션 캐시는 데이터에 가깝고 추론하기 쉬운 반면, 엣지는 멀고, 빠르고, 무효화하기 더 어렵습니다. 데이터를 신선도 필요와 읽기 양으로 분류한 다음 각 부류를 의도적으로 배치하고 설정하여 둘 다 해결하십시오.
팀과 논의할 질문
캐시하는 각 종류의 데이터의 실제 오래됨 허용도는 무엇이며, TTL과 무효화를 습관이 아니라 그 허용도로 정했습니까? 대부분의 팀은 누군가 한 번 고르고 다시 보지 않은 TTL로 캐시하므로, 일부 데이터는 비즈니스가 받아들일 수 있는 것보다 더 오래된 채 제공되고 다른 데이터는 캐시가 거의 도움이 되지 않을 만큼 공격적으로 만료됩니다. 가장 많이 캐시하는 자원 열 개를 가져와 각각에 대해 그 데이터의 소유자에게 안전하게 얼마나 오래될 수 있는지 물으십시오. 몇 초, 몇 분, 몇 시간, 또는 발행되면 영원히. 답이 크게 다르고 현재 TTL이 그것과 맞지 않음을 대개 발견할 것입니다. 원하는 결과는 각 부류를 접근(짧은 TTL, 긴 TTL과 비우기, 버전 관리되는 불변 키)에 대응시킨 짧은 신선도 분류이며, 캐싱 결정이 짐작이 아니라 데이터 의미론에서 따라 나오게 하는 것입니다.
가장 인기 있는 캐시 항목이 피크 트래픽 아래서 지금 만료된다면 원본에는 무슨 일이 일어나겠습니까? 이 질문은 진짜 스탬피드 방어가 있는지 바람뿐인지를 드러냅니다. 많은 시스템이 피크 중에 뜨거운 키가 만료되어 모든 요청이 한꺼번에 데이터베이스로 몰릴 때까지 잘 돌아가며, 캐시를 방패에서 방아쇠로 바꿉니다. 가장 바쁜 엔드포인트의 경로를 구체적으로 따라가 보십시오. 원본에 닿는 미스가 하나뿐이도록 요청 병합이 있습니까, 항목이 일제히 만료되지 않도록 지터가 있습니까, 사용자가 채움을 기다리지 않도록 stale-while-revalidate 정책이 있습니까? 직관이 아니라 부하 테스트 증거를 가져오십시오. 사용자 열 명에서 버티는 스탬피드 방어도 만 명에서 무너질 수 있기 때문입니다. 확신 있게 답할 수 없다면, 다음 복원력 투자를 방금 찾은 것입니다.
어떤 공유 캐시도 한 사용자의 비공개 데이터를 다른 사용자가 칠 수 있는 키 아래 저장하지 않는다고 확신합니까? 이것이 보안 인시던트와 헤드라인이 되는 캐싱 실수입니다. 개인화되었거나 인증된 응답이 사용자 신원을 빠뜨린 키로 캐시되거나, 응답을 비공개로 유지하려는
Cache-Control헤더가 없어서 공유 프록시나 CDN이 그것을 저장하고 다음 사람에게 제공할 때 일어납니다. 공유 계층에서 어떤 응답이 캐시 가능한지 감사하고, 모든 사용자별 응답이private이나no-store로 표시되어 있는지 확인하고, 모든 캐시 키가 응답을 바꾸는 모든 입력을 포함하는지 확인하십시오. 영향 범위가 모든 다운스트림 사용자이므로 이를 보안 리뷰로 다루고 4.2장의 실천에 연결하십시오.각 캐시가 실제로 어떤 쓰기 패턴을 쓰며, 누군가 일부러 골랐습니까? cache-aside, write-through, write-back, write-around는 신선도와 캐시가 실패할 때 잃는 것에 대해 정반대의 약속을 하지만, 대부분의 코드베이스에서 패턴은 첫 작성자가 마침 복사한 것입니다. 큰 팀에서 이것이 중요한 이유는 한 서비스는 cache-aside의 신선도를 가정하고 다른 서비스는 조용히 write-back을 돌리면, 손상된 것처럼 보이지만 단지 오래된 데이터가 생겨 호출 대기 엔지니어가 유령을 쫓느라 몇 시간을 낭비할 수 있기 때문입니다. 캐시별 목록을 가져오십시오. 쓰기 패턴, 보장하는 신선도, 프로세스가 죽으면 플러시되지 않은 쓰기에 일어나는 일. 어느 캐시든 write-back을 쓴다면 그것을 뒷받침하는 내구성 이야기를 가져오십시오. 재무나 기록 데이터를 다루는 기업과 정부 시스템에서 뒷받침 보장이 없는 write-back 캐시는 곧 나올 감사 지적이므로, 논의는 각 캐시의 패턴이 지명되고, 정당화되고, 적히는 것으로 끝나야 합니다.
배포하거나 데이터를 바꿀 때 관련된 모든 캐시 계층이 올바르게 무효화됩니까, 아니면 누군가 비우기를 기억하는 데 의존합니까? 무효화는 어려운 부분이며 실패 방식은 조용합니다. 계층 구조의 한 층, 곧 CDN, 리버스 프록시, 애플리케이션 캐시가 메시지를 받지 못해 수정된 값이 몇 시간 동안 틀린 채로 남습니다. 대규모 조직은 이 위험을 곱절로 합니다. 하나의 논리적 변경이 서로 다른 팀이 소유한 여러 리전의 많은 캐시에 전파되어야 할 수 있기 때문입니다. 최근 데이터 변경 하나의 구체적 추적을 가져와 모든 캐시 계층을 따라가며 각각에서 물으십시오. 여기서 무엇이 무효화를 촉발했고, 얼마나 걸렸는가? 무효화를 이름 짓기(버전 키)나 이벤트(변경이 비우기를 발행)로 바꾸는 설계를 수동 런북보다 선호하십시오. 세율이나 급여액 같은 공표된 잘못된 수치가 법적 무게를 지닐 수 있는 공공 부문 시스템에서 무효화 간극은 불편이 아니라 컴플라이언스 노출이므로, 결과는 캐시된 데이터의 모든 부류에 대한 지도화된 무효화 경로여야 합니다.
캐싱을 공유 플랫폼 인프라로 다루고 있습니까, 아니면 모든 팀이 키, 무효화, 스탬피드 방어를 따로 다시 발명하고 있습니까? 잘된 캐싱은 한 번 해결된 소수의 어려운 문제입니다. 정규화된 키, 이벤트 기반 무효화, 요청 병합, 올바른 HTTP 의미론, 계층별 관측 가능성입니다. 각 팀이 이를 즉흥적으로 하면 대규모 조직은 같은 실수에 반복해서 값을 치르며, 한 서비스에서 고친 오염 버그나 비공개 데이터 유출이 열 개의 다른 서비스에서는 조용히 남습니다. 오늘 캐싱 관례를 누가 소유하고 서비스 전반에 중복된 캐싱 코드가 얼마나 있는지 정직한 지도를 가져오십시오. 경쟁하는 고려는 자율입니다. 팀들은 의무화된 공유 라이브러리에 저항하므로, 채택하기 쉬운 포장된 길의 기본값과 시행되는 단단한 표준을 저울질하십시오. 기업이나 정부 플랫폼 그룹에게 공유되고 잘 테스트된 캐싱 능력은 보안과 감사 요건이 균일하게 성립하게 하는 가장 싼 방법이기도 하므로, 논의는 무엇이 공유 인프라가 되고 누가 재원을 대는지 결정해야 합니다.
분야별 관점
스타트업. 캐싱은 아직 규모를 키울 여유가 없는 트래픽 급증에서 살아남는 가장 싼 길이므로, 가진 적은 시간을 몇 가지 지렛대 효과가 큰 배치에 쓰십시오. 정적 자산을 위한 버전 URL을 가진 CDN, 가장 뜨거운 질의 앞의 짧은 TTL과 지터를 가진 단일 cache-aside 계층입니다. 직접 운영하기보다 관리형 CDN과 캐시 서비스에 기대고, 요청 병합을 일찍 더하십시오. 작은 데이터베이스를 향한 출시일 스탬피드가 좋은 하루를 나쁘게 끝낼 가능성이 가장 높은 실패이기 때문입니다. 중요하다는 데이터가 알려 주기 전까지는 정교한 무효화 체계를 건너뛰십시오.
소기업. 캐싱 전문가도 빠듯한 예산도 없으니, 이미 운영하는 도구 안에서 공짜로 얻는 캐싱을 사는 쪽을 선호하십시오. 호스팅에 포함된 CDN, 웹 프레임워크 응답의 HTTP Cache-Control 헤더, 데이터베이스의 내장 질의 캐시입니다. 사느냐 만드느냐는 여기서 거의 항상 사는 쪽을 선호합니다. 잘못 키가 지정되어 한 고객의 데이터를 다른 고객에게 유출하는 캐시는 피한 관리형 서비스보다 훨씬 많이 들기 때문입니다. 두 가지 싼 이득, 곧 올바른 HTTP 헤더와 공유 계층에서 인증된 페이지를 절대 캐시하지 않는 것을 맞게 하고, 이국적인 패턴은 내버려 두십시오.
대기업. 많은 팀에 걸친 규모에서는 위험이 어느 하나의 캐시에서 그것들 사이의 불일치로 이동합니다. 갈라진 키 체계, 고르지 않은 무효화, 한 서비스에는 나타나고 다른 서비스에는 없는 비공개 데이터 유출입니다. 키, 무효화, 스탬피드 방어, 계층별 관측 가능성에 대한 포장된 길의 기본값을 가진 공유 플랫폼 인프라로 캐싱을 제공해, 적중률, 축출, 오래됨이 한 곳에서 보이고 균일하게 다스려지게 하십시오. 캐시 설정을 보안에 민감한 코드로 리뷰 가능하게 만들고, 리전 간 무효화를 팀별 런북이 아닌 일급 설계 문제로 다루십시오.
정부. 조달과 투명성 제약이 무엇을 캐시할 수 있고 그것이 안전함을 어떻게 입증하는지를 형성합니다. 공개 콘텐츠, 곧 안내문, 양식, 요율표를 긴 TTL로 CDN 뒤에서 공격적으로 캐시해 신고 마감일 급증이 원본에서 멀리 떨어진 곳에서 흡수되게 하고, 그 설정을 감사를 위해 문서화하십시오. 시민 자신의 기록을 보여 주는 인증된 페이지는 공유 캐시에 결코 닿아서는 안 되며, 그 규칙은 단지 단언되는 것이 아니라 검증 가능해야 합니다. CDN이나 캐싱 서비스를 벤더에게서 조달하는 곳에서는 계약이 필요한 통제(키 지정, 비우기, 로깅)를 드러내도록 요구하고, 공공 데이터를 독점 캐시 형식 뒤에 가두는 종속을 피하십시오.
사례
스타트업. 작은 소비자 앱이 상품 카탈로그를 인메모리 저장소가 받치는 cache-aside 계층으로 돌리며, 60초 TTL과 항목이 함께 만료되지 않도록 지터를 둡니다. 정적 자산은 버전 파일명과 1년 max-age를 가진 CDN으로 가서, 스타일시트를 바꾸는 배포가 새 URL을 제공하고 비우기가 필요 없습니다. 인기 팟캐스트의 출시가 트래픽 급증을 보내자, single-flight 병합 덕에 수천 개의 동시 홈페이지 미스가 수천 번이 아닌 한 번의 데이터베이스 읽기를 일으킵니다. 창업자들은 캐싱에 거의 돈을 쓰지 않았지만 작은 데이터베이스를 녹였을 급증을 처리합니다. 잘 고른 몇 개의 캐시를 의도적으로 배치했기 때문입니다.
대기업. 한 글로벌 소매업체가 이미지와 캐시 가능한 API 응답용 CDN, 각 리전의 공유 리버스 프록시 캐시, 계산된 가격과 재고 조각용 애플리케이션 캐시라는 계층화된 캐시로 수백만 쇼핑객을 제공합니다. 캐시 키는 정규화되어 통화, 언어, 기기 등급을 포함하므로 개인화가 유출되지 않고 적중률이 높게 유지됩니다. 상품 데이터는 이벤트 기반 무효화가 있는 짧은 TTL을 써서, 가격 변경이 메시지 버스에 발행되어 몇 초 안에 리전 전반의 영향받는 키를 비웁니다. 모든 계층이 9.2장의 관측 가능성 플랫폼에 적중률, 축출 비율, 오래됨을 보고하며, 떨어지는 적중률에 대한 경보가 한번은 크기가 작은 캐시를 결제 장애가 되기 전에 잡았습니다.
정부. 한 국가 세무 기관이 1년 대부분 조용하다가 마감 근처에 압도되는 신고 포털을 운영합니다. 팀은 안전한 곳에서는 공격적으로, 그렇지 않은 곳에서는 절대 캐시하지 않습니다. 공개 콘텐츠(안내 페이지, 양식, 요율표)는 긴 TTL과 버전 URL로 CDN에서 제공되어 마감일의 읽기 급증을 원본에서 멀리 떨어진 곳에서 흡수합니다. 시민 자신의 신고를 보여 주는 인증된 페이지는 no-store로 표시되어 공유 캐시에 결코 닿지 않으므로 어떤 납세자도 다른 사람의 데이터를 제공받지 않습니다. 캐시 설정은 4.2장의 실천에 비추어 보안에 민감한 코드로 리뷰되고, 스탬피드 방어는 몇 달 전에 마감 규모로 부하 테스트되어, 1년 중 가장 바쁜 날 무너지던 포털이 이제 버팁니다.
비즈니스 사례: 동기, ROI, TCO
캐싱의 수익은 이례적으로 직접적이고 정량화하기 쉽습니다. 적중률을 80에서 95퍼센트로 올리는 캐시는 원본 트래픽을 4분의 3 줄이며, 이는 데이터베이스 업그레이드를 미루거나, 더 적은 애플리케이션 서버를 운영하거나, 그렇지 않았다면 긴급 확장이 필요했을 트래픽 급증에서 살아남는 것을 뜻할 수 있습니다. 지연 개선은 상거래에서는 매출로, 공공 서비스에서는 만족도와 완료율로 전환되며, 연구는 오래전부터 더 빠른 페이지를 더 높은 전환과 더 낮은 이탈과 연결해 왔습니다. 엣지에서 제공되는 요청은 원본 대역폭이나 처리에 값을 치르지 않으므로 송신과 컴퓨트 비용이 떨어집니다. 대부분의 시스템인 읽기 위주 시스템에서 캐싱은 흔히 살 수 있는 가장 싼 성능입니다.
총소유비용을 정직하게 따져 보십시오. 직접 비용은 소소합니다. CDN과 캐시 인프라는 아껴 주는 원본 용량에 비해 쌉니다. 진짜 비용은 엔지니어링 규율입니다. 틀린 캐시는 캐시가 없는 것보다 나쁘기 때문입니다. 오래됨 버그, 무효화 실수, 캐시 오염 취약점은 모두 실제 비용이 들며, 캐싱이 공유되고 잘 테스트된 능력으로 제공되는 대신 팀별로 즉흥적으로 이루어질 때 커집니다. 가장 강한 비즈니스 사례는 소량의 공유 캐싱 인프라와 관례(표준 키, 무효화, 스탬피드 방어, 관측 가능성)에 재원을 대어 모든 팀이 실수를 반복하지 않고 이점을 얻게 하는 것입니다. 리더십에게 구성하면 캐싱은 그들이 이미 추적하는 지표, 곧 인프라 비용, 페이지 지연, 전환 및 완료율, 피크 행사 중 인시던트 빈도에 연결됩니다.
안티패턴과 함정
- 무효화 없는 캐싱: 비울 방법 없이 긴 TTL을 설정해 수정된 값이 몇 시간 동안 틀린 채로 남는 것.
- 너무 굵은 키: 개인화된 응답을 공유 키로 캐시해 한 사용자의 데이터를 다른 사용자에게 유출하는 것.
- 너무 잘은 키: 키에 휘발성 입력을 넣어 두 요청이 결코 일치하지 않고 적중률이 무너지는 것.
- 스탬피드 방어 없음: 뜨거운 키가 부하 아래서 만료되어 모든 요청이 한꺼번에 원본으로 몰리는 것.
- 동기화된 만료: 함께 쓰인 항목 묶음이 지터 없이 같은 순간 모두 만료되어 주기적 천둥 떼를 일으키는 것.
- 공유 계층에 비공개 데이터 캐시:
Cache-Control: private이나no-store가 없어 프록시나 CDN이 인증된 응답을 저장하는 것. - 키에 없는 입력 무시: 헤더를 응답 본문에 반영하면서 키에서는 빠뜨려 캐시 오염의 문을 여는 것.
- 작은 캐시: 작업 집합을 담기에 너무 작은 캐시가 스래싱하며 항목이 필요하기 직전에 축출하는 것.
- 측정되지 않는 캐시: 적중률이나 축출 지표가 없어 저하되는 캐시가 장애가 될 때까지 보이지 않는 것.
- 내구성 없는 write-back: 뒷받침 보장 없이, 플러시 전에 캐시가 죽으면 사라지는 빠른 쓰기.
성숙도 모델
- 1단계, 시작: 캐싱이 즉흥적이고 개발자별이며, 무언가 느리게 느껴질 때 반응적으로 추가됩니다. TTL은 짐작이고, 키는 일관되지 않고, 무효화는 수동이거나 없으며, 오래된 데이터와 알 수 없는 버그가 흔합니다. 아무도 적중률을 추적하지 않고, 캐시가 흡수했어야 할 트래픽 급증이 대신 장애를 일으킵니다.
- 2단계, 발전: 팀들이 명백한 곳에 캐시하고 정적 자산에 CDN을 씁니다. 기본 TTL과 cache-aside가 나타나지만, 관례는 서비스마다 다르고, 무효화는 일관되지 않고, 스탬피드 방어가 없고, 비공개 대 공유 캐싱 규칙은 비공식적이며, 관측 가능성은 가끔의 점검에 그칩니다.
- 3단계, 표준화: 캐싱 전략이 문서화되어 조직 전체에서 시행됩니다. 캐시 키가 정규화되고, TTL은 공유 신선도 분류를 따르고, 무효화는 중요한 곳에서 이벤트 기반이며, 스탬피드 방어와 올바른 HTTP 의미론이 표준이고, 비공개 데이터는 공유 계층에 결코 캐시되지 않으며, 모든 계층이 공통 관측 가능성 파이프라인에 적중률과 축출을 보고합니다.
- 4단계, 관리: 캐싱이 기준선에 대해 측정되고 통제됩니다. 각 계층이 목표 적중률, 오래됨 예산, 축출 임계값을 지니고, 대시보드는 적중률이 떨어지거나 축출 비율이 기준선을 넘어 오르면 경보를 냅니다. 스탬피드 방어가 피크 규모로 부하 테스트되고, 캐시별 원본 부하 감소가 정량화되며, TTL과 축출 정책은 측정된 접근 패턴으로 조정되고, 캐시 설정은 릴리스 전에 보안에 민감한 코드로 리뷰됩니다.
- 5단계, 오케스트레이션: 캐싱이 조직 전체에서 지속적으로 개선되고 통합됩니다. 배치, 키, TTL이 변하는 트래픽에 적응하고, 엣지 컴퓨트는 값을 하는 곳에 쓰이고, 용량 계획과 비용 모델이 캐시 지표를 활용하며, 한 팀의 인시던트에서 얻은 교훈이 공유 관례에 공급됩니다. 조직은 캐싱을 지역적 편법의 모음이 아니라 설계되고, 측정되고, 적응하는 능력으로 다룹니다.
논의를 위한 아이디어
- 시스템에서 지금 차가워진다면 원본을 가장 위태롭게 할 캐시는 어느 단일 캐시이며, 무엇이 그것을 보호합니까?
- 캐시 계층의 각 층에 대해 현재 적중률을 기억으로 말할 수 있습니까? 없다면 그것이 무엇을 알려 줍니까?
- 버전 키로 무효화 문제를 이름 짓기 문제로 바꾼 곳은 어디이며, 아직 그럴 수 있는 곳은 어디입니까?
- 쓰기 경로 중 어느 것이 cache-aside, write-through, write-back, write-around를 쓰며, 각각 일부러 골랐습니까?
- 공격자가 요청 헤더 하나를 통제한다면, 사용자가 공유하는 캐시된 응답을 오염시킬 수 있습니까?
- 적중률이 조용히 20포인트 떨어졌음을 몇 분 안에 어떻게 알겠습니까?
핵심 요점
- 캐싱은 지연, 부하, 비용을 줄이며, 캐시 계층(클라이언트, CDN과 엣지, 리버스 프록시, 애플리케이션, 데이터베이스)은 안전하게 가능한 한 위쪽과 바깥쪽에서 답하게 해 줍니다.
- 무효화는 어려운 부분입니다. 캐시 키와 TTL을 의도적으로 설계하고, 가능한 곳마다 버전 키로 무효화 문제를 이름 짓기 문제로 바꾸십시오.
- 요청 병합, 지터, stale-while-revalidate로 부하 아래서 캐시를 보호하십시오. 캐시는 스탬피드가 깨뜨릴 수 있는 바로 그 순간 가장 필요하기 때문입니다.
- 워크로드마다 쓰기 패턴과 축출 정책을 고르고, 캐시가 짐작하게 두지 말고 HTTP 캐싱 의미론(
Cache-Control, ETag, 검증)을 명시적으로 쓰십시오. - 캐시된 콘텐츠를 공격 표면으로 다루고 적중률, 축출, 오래됨을 측정하십시오. 관측되지 않거나 키가 잘못된 캐시는 자산이 아니라 숨은 부채입니다.
참고 문헌과 더 읽을거리
- Martin Kleppmann, Designing Data-Intensive Applications
- Andrew S. Tanenbaum and Herbert Bos, Modern Operating Systems
- John L. Hennessy and David A. Patterson, Computer Architecture: A Quantitative Approach
- Roy T. Fielding and Julian Reschke, “Hypertext Transfer Protocol (HTTP/1.1): Caching,” RFC 7234, IETF
- Mark Nottingham, “Caching Tutorial for Web Authors and Webmasters”
- James Kettle, “Practical Web Cache Poisoning,” PortSwigger Research
- Betsy Beyer, Chris Jones, Jennifer Petoff, and Niall Richard Murphy (eds.), Site Reliability Engineering: How Google Runs Production Systems
- Michael T. Nygard, Release It! Design and Deploy Production-Ready Software