🌏 CDN(Content Delivery Network)
✅ 개념

정적 콘텐츠를 전송하는 데 쓰이는 지리적으로 분산된 서버의 네트워크
우리가 평소 쓰는 정적 파일(이미지, JS, CSS, 비디오 등)을 캐시해, 더 가까운 곳에서 보내주기 위해 존재하는 인프라입니다.
(최근에는 동적 컨텐츠를 캐싱하는 데도 CDN을 사용하지만, 이 경우는 다음 포스팅에서 소개하겠습니다)
정적인 파일을 미국 서버에서 가져오면 200ms 이상 걸리지만, 서울에 있는 POP(Point of Presence)에서 가져오면 10~20ms 정도로 끝나기 때문에 속도가 확 달라지는 것이죠.
Origin Server: 원본 컨텐츠를 저장하는 중앙 서버
Edge Server: 전세계 곳곳에 위치한 분산 서버로, 오리진 서버의 컨텐츠를 캐싱함
DNS 서버: 오리진 서버에 요청을 보내면 DNS 서버가 가장 가까운 엣지 서버로 라우팅함

✅ CDN 서비스
- AWS CloudFront
- Google Cloud CDN
- Azure CDN (Azure Front Door 포함)
- Oracle Cloud CDN
🌏 CDN의 특징
✅ 지리적으로 가까우닛깐 빠르다
CDN을 쓰면 서울에서 한 요청을 미국 서버에서 가져오는 것이 아닌, 그보다 가까운 서울/도쿄의 CDN으로부터 가져오게 됩니다.
사용자 → 가까운 서울/도쿄 POP → 응답
이 구조 덕분에 반응 속도가 매우매우매우 빨라집니다.
***POP(Point of Presence): CDN이 전 세계에 설치한 지역 캐시 서버 거점
✅ 이미 캐싱해둔 파일을 바로 준다
한 번 누가 읽어온 파일은 POP에 저장됩니다.
그다음 사용자들은 origin 서버를 거치지 않고 바로 POP에서 응답받습니다. = 빠르다
✅ 압축, 최적화, HTTP/2/3 등을 적용해 빠르다
- Brotli 압축
- CDN은 파일을 사용자에게 보내기 전에 자동으로 압축합니다.
- 더 작은 파일 = 더 빠른 속도
- HTTP/2 멀티플렉싱
- 비교적 최신 프토콜을 활용해 브라우저가 더 많은 파일을 더 빠르게 받아올 수 있음
- keep-alive 재활용
- 브라우저와 서버가 한번 연결한 TCP 연결을 계속 유지해서 여러 요청에 재사용
- 이미지 포맷 자동 변환(WebP 등)
- CDN이 파일 자체를 변환해서 더 빠르게 만드는 기능
- Cloudflare, Akamai, CloudFront 등은 이미지 포맷 자동 변환, 불필요한 메타데이터 제거, JS/CSS 자동 Minify(공백이나 주석을 제거함), 큰 이미지를 리사이징, HTML 최적화 등등을 제공합니당
사용자가 따로 설정하지 않아도 빠르게 동작합니다.
🌏 CDN 캐싱 전략
CDN을 설정하더라도, 캐시 여부와 자세한 캐시 설정은 결국 서버가 보내는 헤더로 결정됩니다.
CDN은 서버가 보낸 캐시 관련 헤더들을 보고 캐시를 할지, 언제까지 할지, 검증을 어떻게 할지 등등의 세세한 세팅을 하게 됩니다.
결국 정책은 서버가 만들고, CDN은 서버가 정한대로 캐싱만 함!!!
✅ Cache-Control 헤더
파일·응답을 얼마나 어떻게 캐시할지를 결정하는 핵심 헤더
- public: CDN·브라우저 모두 캐시 가능
- private: 브라우저만 캐시 가능 (CDN 캐시 금지, 개인화된 응답인 경우)
- no-store: 어떤 캐시에도 저장 금지 (민감 데이터)
- no-cache: 캐시는 해도 되지만 사용 전 반드시 서버 재검증 필요 (HTML에서 많이 사용)
- CDN은 원본에 HEAD 요청을 보내 ETag, Last-Modified 헤더 등을 확인하고 같으면(=캐시 히트) POP 캐시를 재사용하고, 다른다면 다시 Origin에서 가져오게 됩니당
- max-age=초: 캐시 유효기간(초 단위)
- 정적 파일은 보통 1년(31536000초)
- s-maxage=초: CDN(공유 캐시)에만 적용되는 max-age
- 브라우저와 다르게 설정할 때 사용
- CDN/프록시만 이 값을 인식하고 브라우저는 이 값을 무시하고 max-age만 사용합니다.
- must-revalidate: 만료된 캐시는 반드시 재검증해야 함
- immutable: 파일이 절대 바뀌지 않음(재검증도 안 함)
- 빌드된 JS/CSS 파일에 사용
- stale-while-revalidate=초: 만료 캐시 사용 + 백그라운드 갱신
- stale-if-error=초: 서버 에러 시 만료 캐시로 대체 응답
사용 예시
Cache-Control: public, max-age=31536000- 정적 파일(JS/CSS/이미지). 누구에게나 동일한 콘텐츠.
Cache-Control: private, max-age=0- 사용자별로 다른 응답(마이페이지, 장바구니 등).
Cache-Control: no-store- 민감 데이터(로그인/결제/개인정보). 절대 저장 금지.
Cache-Control: no-cache- HTML 같은 자주 변경되는 문서. 사용 전 항상 서버 확인.
Cache-Control: public, max-age=600- 짧게 캐싱해도 되는 공용 API(인기글, 환율 등).
Cache-Control: public, s-maxage=300, max-age=0- CDN에는 캐싱하되 브라우저는 캐싱 금지할 때.
Cache-Control: max-age=60, must-revalidate- 만료된 데이터 절대 쓰면 안 되는 경우.
Cache-Control: public, max-age=31536000, immutable- 빌드된 JS/CSS 처럼 절대 바뀌지 않는 정적 파일.
Cache-Control: public, max-age=10, stale-while-revalidate=30- 약간 오래된 데이터를 먼저 주고 백그라운드로 갱신하고 싶은 API.
Cache-Control: public, max-age=10, stale-if-error=300- 서버가 오류일 경우 캐시된 데이터라도 보여야 할 때.
✅ ETag 헤더
파일의 버전·식별자 역할을 하는 헤더
- ETag: 파일의 버전/식별자
- If-None-Match: ETag가 같으면 304(변경 없음) 응답
사용 예시
- 서버:
ETag: "abc123"/ 클라이언트:If-None-Match: "abc123"- 파일이 “바뀌었는지” 확인할 때. 변경이 잦은 파일.
동작 방식
- 서버가
ETag: "abc123"로 응답 - 브라우저가 다음 요청에
If-None-Match: "abc123" - 서버가 같으면
304 Not Modified응답 (다운로드 생략)
✅ Last-Modified 헤더
파일이 마지막으로 수정된 시간을 나타내는 헤더
- Last-Modified: 파일의 마지막 수정 시간
- If-Modified-Since: 수정 여부 비교 후 304 응답
사용 예시
- 서버:
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT - 클라이언트:
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT- 날짜 기반으로 변경 여부만 체크하면 되는 파일
동작 방식
- 파일이 안 바뀌면 304 응답
- 바뀌면 새 파일 내려줌
✅ Expires 헤더 [Deprecated]
Cache-Control 이전에 쓰던 구 방식으로 지정한 날짜나 시간까지 캐싱한다
- Expires: 캐시 만료일을 특정 날짜로 지정 (Cache-Control보다 우선순위 낮음)
- 현대 CDN은 Cache-Control이 우선
- Cache-Control이 없을 때만 사용됨
✅ Vary 헤더
캐시를 구분할 기준을 지정하는 헤더
- Accept-Encoding: 압축 방식별로 캐시 분리
- Accept-Language: 언어별로 캐시 분리
- User-Agent: 기기별 캐시 분리(PC/모바일 등)
- Cookie: 요청마다 캐시가 달라져 캐싱 거의 불가(주의)
사용 예시
Vary: Accept-Encoding- gzip/br 등 압축 포맷별로 캐시 구분
Vary: Accept-Language- 언어별로 다른 HTML을 제공할 때
Vary: User-Agent- 모바일과 데스크탑을 다르게 캐싱할 때
Vary: Cookie- 거의 하지 않음. 요청마다 캐시가 달라져 캐싱 무효화
✅ Content-Encoding 헤더
서버/CDN이 응답을 어떤 방식으로 압축했는지 나타내는 헤더
- gzip: 일반적 압축
- br: Brotli 압축(가장 효율적)
- deflate: 오래된 압축 방식
🌏 CloudFront vs Cloudflare
✅ CloudFront = CDN
- AWS 생태계에 최적화된 CDN
- CDN 역할이 중심인 일반적인 CDN이라 origin IP가 그대로 노출될 수 있음. (단독 사용은 보안에 쪼매 취약)
구조
- S3, ALB, EC2 등 Origin을 내가 완전히 소유할 수 있는 구조
- 클라이언트가 직접 CloudFront URL에 접속
- DNS는 Route53과 자연스럽게 연동
✅ Cloudflare = CDN + 보안 + 프록시 + 네트워크
- 보안 + 프록시 + CDN + Edge 컴퓨팅이 결합된 서비스
- 보안 중심 설계
- 웹사이트 전체 앞단을 보호하는 WAF 프록시 역할(리버스 프록시)을 통해서 Origin IP를 숨길 수 있음
- WAF, Rate Limit, Bot Management, Zero Trust, Firewall 전부 제공
🌏 CDN 쓸 때는 보안을 조심해야 한다!!!
✅ CDN의 Origin IP를 감춰야 한다
Origin IP를 감추는 것이 가장 중요합니다.
Cloudflare 같은 서비스를 앞에 두는 목적은 공격을 대신 받아주는 것인데, 만약 누군가 Origin 서버의 IP를 알게 되면 Cloudflare를 우회해서 직접 서버에 공격을 보낼 수 있습니다. 그러면 다른 정보를 읽을 수도, CDN 서버에 DDoS 공격을 보내게 될 수도 있으므로 이렇게 되면 CDN을 쓰는 의미가 거의 사라지게 됩니다.
따라서 Origin CDN의 방화벽에서는 Cloudflare의 IP Range만 허용하고 나머지는 전부 차단해야 합니다.
✅ 개인화된 데이터는 캐시에 들어가면 안 됨
이런 데이터들은 다른 사람과 공유하지 않습니다
- 로그인한 사용자가 보는 HTML
- 쿠키 기반 인증 페이지
- 개인정보 포함된 GET 요청
여기에 실수로 Cache-Control: public을 달아버리면 다른 사용자에게 같은 HTML이 그대로 전달됩니다. (실제로 이런 사고가 꽤 많습니다.)
✅ HTTPS 반드시 유지
CDN ↔ Origin 간도 꼭 HTTPS로 해야 합니다.
✅ Signed URL / Signed Cookie 사용
URL/Coolie에 ‘유효기간 + 파일경로 + 비밀키로 만든 서명’을 붙여서 만료되면 쓸 수 없게 만드는 기술
- CloudFront: Signed URL / Signed Cookies
- Cloudflare: Tokenized URL
이걸 안 쓰면 이미지 링크가 퍼지는 순간 누구나 다운받을 수 있습니다.
🌏 CDN을 써도 왜 서버 부하가 줄어들지 않을까?
✅ 캐시 MISS가 나면 결국 origin으로 간다
캐시에 없으면 무조건 원 서버(origin)로 갑니다.
파일명이 자주 바뀐다든지, 매번 쿼리 파라미터가 달라진다든지(Hard Cache Busting) 하면 미스가 계속 납니다.
✅ 정적 파일이 아닌 요청(API, HTML)은 캐시가 안 됨
대부분의 API는 캐시되지 않습니다. 개인화된 HTML도 캐시가 어렵습니다.
✅ Cache-Control 설정이 잘못된 경우
Cache-Control: private
이러면 CDN이 절대 캐시할 수 없습니다. 많이 발생하는 실수입니다.
✅ 자주 변하는 파일을 캐싱에 태우면 효과가 없다
만약에 배포할 때마다 JS/CSS 파일명을 안 바꾸고 내용만 바꾸는 경우에는 CDN에서는 기존 파일이 변한 걸 몰라서 매번 origin으로 확인하러 감

도움이 되었다면, 공감/댓글을 달아주면 깃짱에게 큰 힘이 됩니다!🌟
비밀댓글과 메일을 통해 오는 개인적인 질문은 받지 않고 있습니다. 꼭 공개댓글로 남겨주세요!
'아키텍처+MSA' 카테고리의 다른 글
| [MSA] MSA 운영 이슈를 해결하는 패턴: SAGA/CQRS/Outbox/Bulkhead/Circuit Breaker/Event Sourcing (0) | 2025.11.27 |
|---|---|
| [MSA] MSA 운영에서 발생하는 이슈들: 데이터 일관성/트랜잭션 적용 불가/장애 전파/이벤트 불일치/조회 성능 (0) | 2025.11.27 |
| [아키텍처/캐시] Redis 캐시 완전 정복: 개념·전략·TTL·일관성·모니터링 총정리 (0) | 2025.11.20 |
| [아키텍처] 로드밸런싱 완전 정복: L4/L7 구조부터 SPOF 해결까지 (0) | 2025.11.20 |
| [아키텍처/Kafka] Kafka로 강한 결합 탈출하기: 회원가입 비동기 처리 미니 프로젝트 (0) | 2025.11.05 |