"CDN 을 사용하고 있으니 브라우저 캐시는 신경 쓰지 않아도 되지 않을까?"
웹 성능을 개선하다 보면 자주 나오는 이야기입니다.
둘 다 이미 받아온 응답을 재사용해서 네트워크 비용과 서버 부하를 줄인다는 점이 같기 때문에,
Cache-Control 만 잘 설정하면 CDN 과 브라우저가 똑같이 캐시해줄 것이라고 생각하기도 쉽습니다.
하지만 두 캐시는 저장되는 위치, 응답을 공유하는 범위, 원본 서버까지 요청이 전달되는 조건이 다릅니다. 이번 글에서는 CDN 캐시와 브라우저 캐시가 각각 어디에서 동작하며, 하나의 HTTP 응답을 두 캐시가 어떻게 나누어 저장하는지 살펴보겠습니다.
HTTP 캐시는 요청에 대한 응답을 저장해두었다가, 같은 요청이 발생했을 때 저장된 응답을 재사용하는 방식입니다.
첫 요청 Client → Server → Response
이후 요청 Client → Cache → Cached Response
캐시가 없다면 같은 이미지, JavaScript 파일, API 응답을 요청할 때마다 서버가 응답을 다시 만들고 네트워크로 전송해야 합니다. 캐시를 사용하면 응답 시간이 짧아지고, 네트워크 전송량이 줄어들며, Origin Server 의 처리 부담도 함께 줄어듭니다. 트래픽이 몰리는 순간에 서버를 보호하는 장치가 되기도 합니다.
HTTP 표준에서는 캐시를 크게 두 가지로 구분합니다.
Private Cache 특정 사용자만 사용하는 캐시 예) Browser Cache
Shared Cache 여러 사용자가 함께 쓰는 캐시 예) CDN Cache
즉, 브라우저 캐시와 CDN 캐시는 완전히 다른 기술이라기보다 HTTP 캐시가 서로 다른 위치와 범위에서 동작하는 형태에 가깝습니다.
Browser Cache 는 사용자의 브라우저가 응답을 로컬에 저장하는 Private Cache 입니다. 사용자가 처음 페이지에 방문하면 브라우저는 서버나 CDN 으로부터 HTML, CSS, JavaScript, 이미지 같은 리소스를 내려받습니다. 이후 같은 리소스가 필요할 때 캐시가 아직 유효하다면 네트워크 요청 없이 로컬에 저장된 응답을 그대로 사용할 수 있습니다.
사용자 브라우저
├── Memory Cache
└── Disk Cache
브라우저 구현에 따라 세부 동작은 다르지만, 일반적으로 메모리 캐시는 빠른 대신 브라우저 세션이 끝나면 사라질 수 있고, 디스크 캐시는 상대적으로 오래 유지됩니다.
Browser Cache 는 특정 사용자의 기기에 저장되기 때문에 다른 사용자는 그 캐시를 사용할 수 없습니다.
대신 캐시가 유효하다면 네트워크 요청 자체가 발생하지 않고, 브라우저 설정이나 강력 새로고침,
저장 공간 정책 등에 의해 언제든 제거될 수 있습니다. 로그인 사용자처럼 개인화된 응답도
private 지시어를 사용하면 브라우저에만 저장하도록 제한할 수 있습니다.
Cache-Control: private, max-age=3600
여기서 private 은 Shared Cache 에는 저장하지 말라는 의미이고,
max-age=3600 은 응답이 생성된 시점부터 3,600초 동안 fresh 한 것으로 간주하라는 의미입니다.
CDN(Content Delivery Network)은 여러 지역에 분산된 Edge Server 를 통해 콘텐츠를 전달하는 네트워크입니다. CDN Cache 는 이 Edge Server 에 원본 응답을 저장하는 Shared Cache 입니다.
Browser → 가까운 CDN Edge → Origin Server
서울에 있는 사용자가 미국의 Origin Server 까지 직접 요청하는 대신, 서울과 가까운 Edge 에서 캐시된 파일을 받는 구조입니다.
CDN 에 요청한 리소스가 있으면 Cache HIT, 없으면 Cache MISS 라고 표현합니다. CDN 에 응답이 없거나 캐시가 만료된 경우에는 Origin Server 까지 요청이 전달되고, CDN 은 Origin 의 응답을 저장한 뒤 브라우저에 전달할 수 있습니다.
Cache MISS
Browser → CDN → Origin Server
↓
Browser ← CDN ← Response
반대로 CDN 에 유효한 응답이 있다면 Origin Server 까지 가지 않고 Edge 에서 바로 반환합니다. 이 응답은 한 사용자만을 위한 것이 아니라, 같은 캐시 키로 요청하는 여러 사용자에게 재사용됩니다.
Cache HIT
Browser → CDN
Browser ← CDN Cached Response
CDN Cache 는 사용자 기기가 아니라 CDN 사업자의 Edge Server 에 저장되고, 여러 사용자가 같은 캐시를 공유합니다. 사용자와 가까운 위치에서 응답하기 때문에 지연 시간이 줄고, Origin Server 로 전달되는 요청과 트래픽도 줄어듭니다. 필요할 때는 CDN 설정이나 API 로 Purge(무효화)할 수 있으며, 응답은 보통 URL 과 쿼리 문자열, 요청 헤더 등으로 구성된 Cache Key 를 기준으로 구분됩니다.
그래서 CDN 을 단순한 캐시 저장소와 같은 개념으로 보기는 어렵습니다. CDN 은 전 세계에 콘텐츠를 전달하는 분산 네트워크이고, 캐싱은 그 콘텐츠를 빠르게 제공하기 위해 사용하는 핵심 기능 중 하나에 가깝습니다.
지금까지의 내용을 정리하면 아래와 같습니다.
Browser Cache
HTTP 캐시 유형 Private Cache
저장 위치 사용자의 브라우저 또는 기기
공유 범위 해당 사용자 한 명
주요 목적 재방문과 반복 요청 최적화
캐시 제거 브라우저 정책 또는 사용자 조작
개인화 응답 조건에 따라 가능
CDN Cache
HTTP 캐시 유형 Shared Cache
저장 위치 CDN Edge Server
공유 범위 같은 Cache Key 로 요청하는 여러 사용자
주요 목적 지리적 지연과 Origin 부하 감소
캐시 제거 TTL 만료, 배포 연동, Purge API 등
개인화 응답 잘못 캐시하면 다른 사용자에게 노출될 위험
가장 큰 차이는 누가 저장된 응답을 재사용하는가 입니다. 브라우저 캐시가 있으면 같은 사용자의 두 번째 요청이 빨라지고, CDN 캐시가 있으면 아직 그 사이트를 방문한 적 없는 사용자도 가까운 Edge 에 저장된 응답을 받을 수 있습니다.
CDN 과 Browser Cache 는 둘 중 하나를 선택하는 관계가 아닙니다. 실제 요청 경로에는 두 캐시가 함께 존재할 수 있습니다.
Browser Cache → CDN Cache → Origin Server
브라우저가 리소스를 요청할 때의 흐름은 아래와 같습니다.
1. 브라우저가 자신의 캐시를 확인
2. 유효한 응답이 있다면 로컬 캐시를 사용
3. 없다면 CDN 으로 요청 전달
4. CDN 에 유효한 캐시가 있다면 Edge 에서 응답
5. CDN 에도 없다면 Origin Server 로 요청
6. 응답은 CDN 과 브라우저의 정책에 따라 각각 저장
예를 들어 A 사용자가 처음 이미지를 요청했고 CDN 에 캐시가 없다면 Origin 까지 요청이 전달됩니다. 이후 B 사용자는 브라우저 캐시가 없어도 CDN 에 저장된 이미지를 받을 수 있고, B 사용자가 같은 이미지를 다시 요청하면 이번에는 자신의 브라우저 캐시에서 바로 가져올 수 있습니다.
A 의 첫 요청 Browser MISS → CDN MISS → Origin
B 의 첫 요청 Browser MISS → CDN HIT
B 의 재요청 Browser HIT
따라서 CDN 을 사용하더라도 Browser Cache 는 여전히 의미가 있습니다. CDN 은 Origin 보다 가깝지만, 브라우저 로컬 캐시는 네트워크 요청 자체를 생략할 수 있기 때문입니다.
브라우저와 CDN 은 주로 HTTP 응답의 Cache-Control 헤더를 보고 캐시 여부와 유효 시간을 판단합니다.
max-age 는 응답이 fresh 한 상태로 유지되는 시간을 초 단위로 지정합니다.
Cache-Control: public, max-age=3600
위 응답은 브라우저와 Shared Cache 에서 1시간 동안 재사용될 수 있습니다.
s-maxage 는 CDN 과 같은 Shared Cache 에만 적용되며, 함께 존재하면 Shared Cache 에서는 max-age 보다 우선합니다.
Cache-Control: public, max-age=60, s-maxage=3600
이 경우 브라우저는 60초, CDN 은 3,600초 동안 응답을 재사용합니다.
사용자에게는 비교적 자주 새 응답을 확인하게 하면서, CDN 은 응답을 더 오래 보관해 Origin 부하를 줄이는 방식입니다.
다만 실제 CDN 이 s-maxage 를 어떻게 처리하는지는 사용하는 서비스의 설정과 문서를 함께 확인해야 합니다.
Cache-Control: public, max-age=3600
public 은 Shared Cache 에도 응답을 저장할 수 있음을 명시합니다.
Cache-Control: private, max-age=3600
반면 private 은 응답을 특정 사용자의 Private Cache 에만 저장하도록 제한합니다.
사용자 이름, 결제 정보, 권한별 데이터처럼 개인화된 응답을 CDN 에 공유하면
다른 사용자에게 그대로 노출될 수 있기 때문에 주의해야 합니다.
여기서 한 가지 짚고 갈 부분은 쿠키가 있다는 사실만으로 응답이 자동으로 private 이 되지는 않는다는 점입니다.
캐시 정책은 응답 헤더와 CDN 설정으로 직접 명시해주어야 합니다.
두 지시어는 이름이 비슷하지만 의미가 다릅니다.
Cache-Control: no-cache
no-cache 는 저장하지 말라는 뜻이 아니라, 응답을 재사용하기 전에 서버에 유효성을 확인하라는 뜻입니다.
Cache-Control: no-store
no-store 는 응답을 캐시에 저장하지 말라는 뜻입니다.
no-cache 저장 가능 재사용 전 매번 재검증 필요
no-store 저장 불가 아예 저장하지 않음
보안상 민감한 응답에는 no-store 가 필요할 수 있지만,
모든 응답에 습관적으로 사용하면 브라우저와 HTTP 캐시의 장점을 그대로 잃게 됩니다.
캐시가 stale 상태가 되었다고 해서 반드시 응답 본문 전체를 다시 내려받는 것은 아닙니다. 브라우저나 CDN 은 조건부 요청(Conditional Request)으로 기존 응답이 여전히 유효한지 확인할 수 있습니다.
서버가 아래와 같이 ETag 를 반환했다고 가정해보겠습니다.
HTTP/1.1 200 OK
ETag: "asset-v1"
Cache-Control: no-cache
그러면 캐시는 다음 요청에 기존 ETag 를 함께 전달합니다.
GET /app.js HTTP/1.1
If-None-Match: "asset-v1"
리소스가 변경되지 않았다면 서버는 본문 없이 응답할 수 있습니다.
HTTP/1.1 304 Not Modified
이때 캐시는 기존에 저장해둔 본문을 다시 사용하고, 리소스가 바뀌었다면 서버가 새로운 본문과 함께 200 OK 를 반환합니다.
Last-Modified 와 If-Modified-Since 도 비슷한 역할을 합니다.
다만 ETag 는 서버가 정한 식별자를 기준으로 비교하기 때문에 콘텐츠 버전이나 해시를 활용할 수 있습니다.
프론트엔드 배포 후 '일부 사용자에게만 이전 화면이 보인다' 면 캐시 계층을 나누어 확인해야 합니다.
Browser Cache
↓
CDN Cache
↓
Origin 또는 배포 서버
Origin 에는 최신 파일이 있어도 CDN 에 이전 응답이 남아 있을 수 있고, CDN 을 Purge 했더라도 사용자의 브라우저에 이전 응답이 남아 있을 수 있습니다. CDN 캐시만 무효화하는 것으로 모든 사용자의 Browser Cache 까지 직접 삭제할 수는 없기 때문입니다. 이미 오래 캐시된 동일 URL 의 리소스는 브라우저에서 만료될 때까지 계속 사용됩니다.
그래서 정적 에셋에는 보통 콘텐츠 해시를 포함한 파일명을 사용합니다.
/assets/app.a1b2c3.js
/assets/app.f7e8d9.js
파일 내용이 바뀌면 URL 도 함께 바뀌기 때문에 기존 캐시와 충돌하지 않고, 긴 캐시 수명을 안전하게 적용할 수 있습니다.
Cache-Control: public, max-age=31536000, immutable
반면 새 에셋의 URL 을 알려주는 HTML 문서는 파일명이 고정되어 있는 경우가 많습니다. HTML 까지 오래 캐시하면 새로 배포한 뒤에도 이전 JavaScript 파일을 가리킬 수 있으므로, 짧게 캐시하거나 재검증하도록 설정하는 방식이 일반적입니다.
Cache-Control: no-cache
정리하면 리소스별로 아래와 같은 전략을 사용할 수 있습니다.
해시가 포함된 JS/CSS/이미지 긴 max-age + immutable
파일명이 고정된 HTML 짧은 TTL 또는 no-cache 로 재검증
사용자별 개인화 응답 private, 필요하다면 no-store
여러 사용자에게 동일한 공개 응답 CDN 캐시 활용 검토
CDN 은 Shared Cache 이기 때문에 Browser Cache 보다 설정할 때 고려할 항목이 많습니다.
캐시는 보통 URL 을 기준으로 응답을 구분하지만, CDN 설정에 따라 쿼리 문자열이나 특정 헤더도 Cache Key 에 포함할 수 있습니다.
/products?page=1
/products?page=2
여기서 page 를 Cache Key 에서 제외하면 서로 다른 요청이 같은 응답을 공유하게 됩니다.
반대로 불필요한 파라미터까지 모두 키에 포함하면 캐시가 지나치게 잘게 나뉘어 HIT 비율이 낮아집니다.
같은 URL 이라도 요청 헤더에 따라 응답이 달라진다면 Vary 를 사용할 수 있습니다.
Vary: Accept-Encoding
이 헤더는 Accept-Encoding 값에 따라 캐시 항목을 구분하라고 알려줍니다.
다만 Vary 대상의 종류가 너무 많으면 캐시 재사용률이 낮아질 수 있습니다.
인증 쿠키나 Authorization 헤더가 사용되는 응답은 CDN 캐시 정책을 특히 신중하게 설정해야 합니다.
사용자별 응답이 동일한 Cache Key 로 저장되면 다른 사용자에게 잘못 전달될 수 있기 때문입니다.
'로그인 요청이니까 CDN 이 알아서 캐시하지 않겠지' 라고 가정하기보다,
응답의 Cache-Control 과 CDN 의 우회(Bypass) 조건을 명시적으로 확인하는 편이 안전합니다.
문제를 진단할 때는 브라우저 개발자 도구의 Network 탭에서 아래 항목들을 확인할 수 있습니다.
(memory cache), (disk cache) 브라우저 캐시 사용 여부
Age 응답이 캐시에 머문 시간
CDN 캐시 상태 헤더 HIT / MISS 여부
Cache-Control, ETag 캐시 정책과 재검증 기준
Last-Modified, Vary
응답 상태 코드 200 인지 304 인지
캐시 상태 헤더의 이름과 의미는 CDN 마다 다르므로 사용하는 서비스의 공식 문서를 확인해야 합니다.
CDN Cache 와 Browser Cache 는 모두 HTTP 응답을 재사용하지만, 서로 다른 계층에서 역할을 나누어 수행합니다.
Browser Cache 사용자의 기기에 저장되는 Private Cache
네트워크 요청 자체를 줄임
CDN Cache 여러 사용자가 공유하는 Edge 의 Shared Cache
Origin 까지 가는 요청을 줄임
max-age 캐시의 freshness 를 제어
s-maxage Shared Cache 의 freshness 를 제어
no-cache 저장 금지가 아니라 재검증
no-store 저장 자체를 하지 않음
정적 에셋 해시 파일명 + 긴 TTL
HTML 짧은 TTL 또는 재검증
그리고 CDN 캐시를 제거해도 이미 저장된 Browser Cache 까지 함께 삭제되는 것은 아니라는 점도 기억해두면 좋습니다.
결국 중요한 것은 캐시를 사용할지 말지를 결정하는 것이 아니라, 어떤 응답을 어느 계층에서 얼마나 오래 재사용할 것인지를 설계하는 것입니다.