
CDN: 넷플릭스가 전 세계에서 버퍼링 없이 재생되는 이유 (완전정복)
미국 본사 서버에서 영상을 쏘면 버퍼링 때문에 망합니다. Akamai가 만든 '인터넷 배달 지점' 혁명부터, 일관된 해싱(Consistent Hashing), Edge Computing까지 심층 분석합니다.

미국 본사 서버에서 영상을 쏘면 버퍼링 때문에 망합니다. Akamai가 만든 '인터넷 배달 지점' 혁명부터, 일관된 해싱(Consistent Hashing), Edge Computing까지 심층 분석합니다.
내 서버는 왜 걸핏하면 뻗을까? OS가 한정된 메모리를 쪼개 쓰는 처절한 사투. 단편화(Fragmentation)와의 전쟁.

미로를 탈출하는 두 가지 방법. 넓게 퍼져나갈 것인가(BFS), 한 우물만 팔 것인가(DFS). 최단 경로는 누가 찾을까?

프론트엔드 개발자가 알아야 할 4가지 저장소의 차이점과 보안 이슈(XSS, CSRF), 그리고 언제 무엇을 써야 하는지에 대한 명확한 기준.

이름부터 빠릅니다. 피벗(Pivot)을 기준으로 나누고 또 나누는 분할 정복 알고리즘. 왜 최악엔 느린데도 가장 많이 쓰일까요?

제가 처음 글로벌 웹 서비스를 런칭했을 때의 일입니다.
AWS us-east-1 (미국 버지니아)에 서버를 두고 한국에서 접속했더니 이미지 하나 뜨는 데 3초가 걸렸습니다.
미국 친구는 "엄청 빠른데?"라고 하는데 말이죠.
아닙니다. 이게 제가 처음으로 물리적 한계를 체감한 순간이었습니다.
제가 정말 화가 났던 건, 제 서버가 느린 게 아니라 물리학이 문제라는 거였습니다. 아무리 코드를 최적화해도 이 지연은 못 줄입니다.
그때 이해했다: "서버를 빠르게 하는 게 아니라, 서버를 가까이 보내야 한다."
이 물리적 한계를 극복하기 위해 나온 기술이 바로 CDN(Content Delivery Network)입니다. 결국 이거였다 - 인터넷 속도 문제의 90%는 거리 문제였던 겁니다.
1990년대 후반, 인터넷 트래픽이 폭증하면서 웹사이트 하나 열 때마다 몇 분씩 걸렸습니다. 사람들은 비아냥거리며 "World Wide Web"을 "World Wide Wait"라고 불렀죠.
당시 상황을 이해하려면 이걸 상상해보세요:
1998년, MIT의 응용수학 교수 톰 레이튼(Tom Leighton)과 제자 대니 루인(Danny Lewin)이 이 문제를 수학적으로 접근했습니다.
핵심 아이디어:
그렇게 Akamai Technologies가 설립되었고, 이것이 상용 CDN의 시초입니다.
재미있는 사실: Akamai는 하와이 원주민 말로 "똑똑한"이라는 뜻입니다. 실제로 똑똑한 선택이었죠.
지금은 Cloudflare, AWS CloudFront, Fastly, Akamai 등이 전 세계 인터넷 트래픽의 50% 이상을 처리합니다. 넷플릭스, 유튜브, 페이스북 모두 CDN 없이는 작동 불가능합니다.
Cloudflare만 해도 전 세계 310개 도시에 데이터센터가 있습니다. 여러분이 이 글을 읽는 지금도 여러분 근처 10km 이내에 Cloudflare 서버가 있을 가능성이 높습니다.
제가 처음엔 이 용어가 헷갈렸는데, 프랜차이즈 카페로 비유하니까 와닿았다:
Origin Server (본점): 원본 데이터가 있는 메인 서버
Edge Server (지점): 전 세계 300여 개 도시에 흩어진 캐시 서버
이걸 실제 시나리오로 따라가봅시다:
[한국 사용자] → "logo.png 주세요" → [어디로 가야 하지?]
Step 1: DNS Resolution (가장 가까운 지점 찾기)
# 사용자가 example.com/logo.png를 요청
# 브라우저는 먼저 DNS 조회를 함
$ dig example.com
;; ANSWER SECTION:
example.com. 60 IN A 104.16.132.229 # Cloudflare Anycast IP
여기서 마법이 일어납니다. 이 IP는 하나가 아닙니다. 전 세계 수천 대 서버가 같은 IP를 공유합니다 (Anycast).
BGP(Border Gateway Protocol) 라우팅이 자동으로 가장 가까운 서버로 연결해줍니다:
[서울 엣지 서버 로그]
2025-05-19 14:32:15 KST
Request: GET /logo.png
Cache Status: MISS (처음 요청이라 캐시에 없음)
Action: Fetching from Origin (us-east-1)
Origin Response Time: 285ms
Saved to Cache with TTL: 86400s (24시간)
Response to Client: 320ms (총 소요 시간)
Step 3: 다음 사용자는 빠르다
[서울 엣지 서버 로그]
2025-05-19 14:32:18 KST
Request: GET /logo.png
Cache Status: HIT (캐시에 있음!)
Response Time: 4ms (엣지에서 바로 전달)
285ms → 4ms, 70배 빠름
CDN의 성능은 Cache Hit Rate(캐시 적중률)로 측정됩니다.
Cache Hit Rate = (Cache Hits / Total Requests) × 100%
CDN을 도입하고 캐시 전략을 잘 세우면 Hit Rate를 60%대에서 95%까지 끌어올릴 수 있다. 그렇게 되면 서버 비용과 응답 시간이 크게 줄어든다는 사례가 많다.
어떻게? 다음 섹션에서 설명합니다.
실무에서 CDN이나 분산 캐시를 논할 때 빠질 수 없는 주제입니다. 저도 처음엔 "왜 이게 중요한지" 이해 못 했는데, 실제 상황을 겪어보니 와닿았다.
엣지 서버가 100대 있을 때, logo.png를 어떤 서버에 저장할까요?
def get_server(key):
server_id = hash(key) % 100 # 0~99 중 하나
return f"edge-{server_id}"
# 예시
get_server("logo.png") # edge-42
get_server("video.mp4") # edge-87
문제없어 보이죠? 그런데...
서버 1대가 추가되면?def get_server(key):
server_id = hash(key) % 101 # 이제 101대
# 똑같은 파일인데...
get_server("logo.png") # edge-43 (바뀜!)
get_server("video.mp4") # edge-88 (바뀜!)
결과: 모든 키의 해시값이 바뀌어 대규모 Cache Miss 발생
실제 시나리오:
제가 처음 이걸 겪었을 때 진짜 패닉했습니다. "서버 추가했는데 왜 서비스가 죽어?"
MIT 교수들이 만든 이 알고리즘의 핵심은 "서버가 추가/삭제되어도 대부분의 키는 그대로 유지된다"입니다.
원리: 링(Ring) 구조 0° (= 360°)
│
┌──────┼──────┐
│ ↓ │
Server-A Server-C
│ │
│ Ring │
│ (0-2^32) │
│ │
Server-B ←─────┘
동작 방식:
import hashlib
class ConsistentHash:
def __init__(self):
self.ring = {} # {hash_value: server_name}
self.sorted_keys = []
def add_server(self, server_name):
# 서버를 링에 배치 (여러 개의 가상 노드 생성)
for i in range(150): # 150개 복제본 (Virtual Nodes)
virtual_key = f"{server_name}:{i}"
hash_val = int(hashlib.md5(virtual_key.encode()).hexdigest(), 16)
self.ring[hash_val] = server_name
self.sorted_keys = sorted(self.ring.keys())
def get_server(self, key):
# 키를 해싱
hash_val = int(hashlib.md5(key.encode()).hexdigest(), 16)
# 링에서 시계방향으로 첫 번째 서버 찾기
for ring_key in self.sorted_keys:
if hash_val <= ring_key:
return self.ring[ring_key]
# 끝까지 못 찾으면 첫 번째 서버 (링이 순환하니까)
return self.ring[self.sorted_keys[0]]
# 테스트
ch = ConsistentHash()
ch.add_server("edge-seoul")
ch.add_server("edge-tokyo")
ch.add_server("edge-osaka")
print(ch.get_server("logo.png")) # edge-tokyo
print(ch.get_server("video.mp4")) # edge-seoul
# 서버 추가
ch.add_server("edge-busan")
print(ch.get_server("logo.png")) # edge-tokyo (그대로!)
print(ch.get_server("video.mp4")) # edge-seoul (그대로!)
print(ch.get_server("new-file.jpg")) # edge-busan (새 파일만 새 서버로)
핵심 포인트:
Virtual Nodes (가상 노드): 한 서버를 링에 여러 번 배치 (150개)
서버 추가 시: 인접한 일부 키만 이동
서버 삭제 시: 그 서버 키들만 다음 서버로
AWS DynamoDB도 Consistent Hashing을 씁니다:
결국 이거였다: 확장 가능한 분산 시스템의 핵심은 Consistent Hashing
단순히 정적 파일(이미지, CSS)만 전달하던 시대는 끝났습니다. 이제는 엣지 서버에서 코드를 실행합니다.
[한국 사용자] "이미지를 400x300으로 리사이즈 해줘"
↓
[서울 엣지] "나 그거 못 해. 미국 본사로 가봐"
↓ (200ms)
[미국 오리진] "알았어" → 이미지 리사이징 → 응답
↓ (200ms)
[한국 사용자] 받음 (총 400ms+)
[한국 사용자] "이미지를 400x300으로 리사이즈 해줘"
↓
[서울 엣지] "내가 바로 해줄게!" → V8 엔진으로 코드 실행 → 응답
↓ (15ms)
[한국 사용자] 받음 (총 15ms)
26배 빠름
// Cloudflare Workers = V8 엔진이 전 세계 310개 도시에서 돌아감
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const url = new URL(request.url)
// 1. A/B 테스트 (엣지에서 바로 처리)
const variant = Math.random() < 0.5 ? 'A' : 'B'
// 2. Geo-blocking (국가별 접근 제어)
const country = request.cf.country // Cloudflare가 자동으로 감지
if (country === 'CN') {
return new Response('Not available in your region', { status: 403 })
}
// 3. 이미지 리사이징
if (url.pathname.endsWith('.jpg')) {
const imageRequest = new Request(url, {
cf: { image: { width: 400, quality: 85 } }
})
return fetch(imageRequest)
}
// 4. 커스텀 캐시 키
const cacheKey = new Request(url, {
cf: { cacheKey: `${url.pathname}:${request.headers.get('Accept-Language')}` }
})
return fetch(cacheKey)
}
실제 사용 사례들:
인증 체크: JWT 토큰 검증을 엣지에서
다국어 리다이렉트: Accept-Language 헤더 보고 /ko/ vs /en/ 분기
Bot 차단: User-Agent 보고 악성 크롤러 차단
이미지 최적화: WebP 지원 브라우저엔 WebP, 나머진 JPEG
// CloudFront의 각 단계에서 Lambda 실행 가능
// Viewer Request → Origin Request → Origin Response → Viewer Response
exports.handler = async (event) => {
const request = event.Records[0].cf.request
const headers = request.headers
// 모바일 기기 감지해서 다른 Origin으로
const userAgent = headers['user-agent'][0].value
if (/Mobile|Android|iPhone/i.test(userAgent)) {
request.origin = {
custom: {
domainName: 'mobile-api.example.com',
port: 443,
protocol: 'https'
}
}
}
// URL 정규화 (쿼리 파라미터 정렬로 캐시 효율 향상)
const params = new URLSearchParams(request.querystring)
const sortedParams = Array.from(params.entries()).sort()
request.querystring = new URLSearchParams(sortedParams).toString()
return request
}
저는 이걸 써서 오리진 요청을 60% 줄였습니다. 엣지에서 해결 가능한 건 엣지에서 처리하니까요.
우리가 넷플릭스로 4K 영화를 볼 때, 수 기가바이트 통파일을 다운로드받는 게 아닙니다. 그랬다간 재생까지 1시간이 걸릴 테니까요.
여기서 HLS(HTTP Live Streaming)와 DASH(Dynamic Adaptive Streaming over HTTP)가 등장합니다.
[넷플릭스 영화: 15GB]
↓
"15GB 다 받을 때까지 기다려주세요..."
↓
30분 후
↓
"자, 이제 재생됩니다!"
누가 이렇게 기다립니까?
[원본 영화 2시간 = 15GB]
↓
[트랜스코딩 서버]
↓
┌──────────────────────────────────┐
│ 2초짜리 조각 3,600개로 쪼갬 │
│ │
│ chunk_0000.ts (1080p) - 4MB │
│ chunk_0001.ts (1080p) - 4MB │
│ chunk_0002.ts (1080p) - 4MB │
│ ... │
│ chunk_3599.ts (1080p) - 4MB │
│ │
│ 동시에 다른 화질도 생성: │
│ chunk_0000.ts (720p) - 2MB │
│ chunk_0000.ts (480p) - 1MB │
│ chunk_0000.ts (360p) - 0.5MB │
└──────────────────────────────────┘
↓
[CDN에 전부 업로드]
재생 목록 파일 (master.m3u8):
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080
1080p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1280x720
720p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2000000,RESOLUTION=854x480
480p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1000000,RESOLUTION=640x360
360p/playlist.m3u8
각 화질의 playlist.m3u8:
#EXTM3U
#EXT-X-TARGETDURATION:2
#EXTINF:2.0,
chunk_0000.ts
#EXTINF:2.0,
chunk_0001.ts
#EXTINF:2.0,
chunk_0002.ts
...
네트워크가 느려지면 화질이 1080p에서 720p로 자동으로 바뀌죠?
클라이언트 로직 (의사 코드):class HLSPlayer {
constructor() {
this.currentBandwidth = 0
this.bufferHealth = 0
}
async playVideo() {
// 1. master.m3u8 받아오기
const master = await fetch('master.m3u8')
const qualities = this.parseMaster(master)
// 2. 초기 품질 선택 (보수적으로 중간)
let currentQuality = qualities[2] // 720p
while (true) {
// 3. 다음 청크 다운로드
const startTime = Date.now()
const chunk = await fetch(currentQuality.nextChunk)
const downloadTime = Date.now() - startTime
// 4. 대역폭 측정
const bandwidth = (chunk.size * 8) / (downloadTime / 1000) // bps
this.currentBandwidth = bandwidth
// 5. 버퍼 상태 확인
this.bufferHealth = this.getBufferLength()
// 6. 품질 조정 로직
if (this.bufferHealth < 5 && bandwidth < currentQuality.bandwidth) {
// 버퍼가 5초 미만이고 속도가 느리면 → 화질 낮춤
currentQuality = this.selectLowerQuality(qualities, bandwidth)
console.log('Switching to lower quality:', currentQuality.resolution)
} else if (this.bufferHealth > 20 && bandwidth > currentQuality.bandwidth * 1.5) {
// 버퍼가 충분하고 속도가 빠르면 → 화질 높임
currentQuality = this.selectHigherQuality(qualities, bandwidth)
console.log('Switching to higher quality:', currentQuality.resolution)
}
// 7. 청크 재생
this.appendToBuffer(chunk)
await this.sleep(2000) // 2초 기다림 (청크 길이)
}
}
}
실제 동작 시나리오:
[00:00] 재생 시작 → 720p 선택 (안전한 선택)
[00:10] 대역폭 8Mbps 측정 → 1080p로 업그레이드
[00:45] 지하철 진입, 대역폭 1.5Mbps → 480p로 다운그레이드
[01:12] WiFi 연결, 대역폭 12Mbps → 1080p로 복귀
사용자는 끊김 없이 계속 볼 수 있습니다. 이게 버퍼링 없는 스트리밍의 비밀입니다.
이 모든 조각(수천 개)이 CDN 엣지 서버에 캐싱되어 있습니다:
[한국 사용자 A] → chunk_0042.ts 요청 → [서울 엣지] 있음! → 4ms 응답
[한국 사용자 B] → chunk_0042.ts 요청 → [서울 엣지] 있음! → 4ms 응답
...
[한국 사용자 Z] → chunk_0042.ts 요청 → [서울 엣지] 있음! → 4ms 응답
만약 CDN 없이 미국 오리진에서 직접 받는다면:
CDN 덕분에 우리는 지구 반대편 영화를 실시간처럼 봅니다.
CDN은 보안 장비로도 필수입니다.
[AWS CloudWatch Alarm]
🚨 EC2 CPU 100%
🚨 Network In: 15 Gbps (평소 0.2 Gbps)
🚨 Server Unreachable
DDoS 공격이 들어오면 이런 상황이 벌어진다. 작은 서버(1Gbps 네트워크)에 15Gbps 트래픽이 쏟아지면?
결과: 서버 즉사. 정상 사용자도 접속 불가.
[공격자] 100 Gbps 트래픽 발사
↓
[CDN Global Network]
┌─────────────────────────────────────┐
│ 전 세계 310개 도시, 수천 대 서버 │
│ 총 용량: 200 Tbps (200,000 Gbps) │
│ │
│ 서울 엣지: 3 Gbps 받음 │
│ 도쿄 엣지: 5 Gbps 받음 │
│ 런던 엣지: 2 Gbps 받음 │
│ ... │
│ │
│ → 각 서버는 여유롭게 처리 │
└─────────────────────────────────────┘
↓
[오리진 서버] 정상 트래픽만 받음 (CDN이 필터링)
2) Anycast 라우팅으로 분산
# CDN은 전 세계에서 같은 IP를 광고함 (BGP Anycast)
[공격자 in 중국] → 공격 트래픽 → [가장 가까운 CDN = 홍콩 엣지]
[공격자 in 러시아] → 공격 트래픽 → [가장 가까운 CDN = 모스크바 엣지]
[공격자 in 브라질] → 공격 트래픽 → [가장 가까운 CDN = 상파울루 엣지]
# 공격 트래픽이 자동으로 전 세계로 분산됨
# 각 엣지는 소량만 받아서 처리 가능
3) 악성 패턴 자동 차단
// Cloudflare Firewall Rules 예시
// Rate Limiting: 같은 IP에서 초당 100회 이상 요청 → 차단
if (request.countPerSecond(request.ip) > 100) {
return { action: 'block', reason: 'rate_limit' }
}
// Challenge: 의심스러운 User-Agent → CAPTCHA
if (/curl|bot|python/i.test(request.userAgent)) {
return { action: 'challenge' }
}
// Geo-blocking: 내 서비스는 한국만 → 나머지 차단
if (!['KR', 'US'].includes(request.country)) {
return { action: 'block', reason: 'geo' }
}
// WAF Rule: SQL Injection 패턴 감지
if (/union.*select|drop.*table/i.test(request.url)) {
return { action: 'block', reason: 'sql_injection' }
}
GitHub의 자체 서버로는 절대 못 막습니다. CDN의 글로벌 용량이 필수였죠.
2021년 6월 8일 오전, 제가 아마존 들어가려는데 안 됐습니다. 레딧도 안 되고, 트위치도 안 되고, CNN도 안 되고...
"우리 집 와이파이 문제인가?" → 아니었습니다. 전 세계가 동시에 접속 불능이었습니다.
조사 결과, Fastly CDN의 글로벌 장애였습니다.
영향받은 사이트들:
전 세계 인터넷 트래픽의 약 10%가 먹통.
놀랍게도 해킹도, 하드웨어 고장도 아니었습니다.
Timeline:[05:00 UTC] 한 고객사가 서비스 설정 변경
↓
[설정 내용] Varnish VCL (CDN 캐싱 로직) 파일에 정규표현식 추가
↓
[05:47 UTC] 이 설정이 전 세계 엣지 서버로 자동 배포됨
↓
[문제] 특정 HTTP 헤더를 만나면 정규표현식 무한 루프 발생 (ReDoS)
↓
[결과] CPU 100% 사용 → 서버 Hang → 전 세계 85% 엣지 서버 다운
↓
[05:58 UTC] Fastly 긴급 롤백
↓
[06:44 UTC] 서비스 정상화
문제의 정규표현식 (추정):
# 의도 - URL 파라미터 추출
/(\?|&)([^=]+)=([^&]*)/
# 하지만 특정 입력에서 Catastrophic Backtracking 발생
# 예: "?a=1&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"
# → CPU가 수십 초 동안 멈춤 (ReDoS)
CDN 하나에만 의존하면 그 CDN이 죽었을 때 대안이 없습니다.
해결책: Multi-CDN 전략# nginx 설정으로 여러 CDN 사용
upstream cdn_pool {
server cdn1.cloudflare.com:443 weight=3;
server cdn2.fastly.com:443 weight=2;
server cdn3.akamai.com:443 backup; # 평소엔 안 씀, 비상용
}
server {
location /static/ {
proxy_pass https://cdn_pool;
proxy_next_upstream error timeout http_503; # 실패하면 다음 CDN으로
}
}
대기업 전략:
코드가 아닌 설정(Config) 변경 하나가 전 세계를 멈출 수도 있습니다.
Best Practice:# Fastly는 이 사고 후 이런 시스템 도입 (추정)
deployment:
strategy: canary
stages:
- percent: 1
duration: 10m
error_threshold: 0.1% # 에러율 0.1% 넘으면 중단
- percent: 10
duration: 30m
error_threshold: 0.5%
- percent: 100
auto_rollback: true
이 사건으로 저는 깨달았습니다: 인터넷은 생각보다 취약하다. 그리고 CDN은 생각보다 중요하다.
Phil Karlton의 명언:
"컴퓨터 과학에는 두 가지 어려운 문제가 있다: 캐시 무효화와 이름 짓기."
제가 가장 골머리 앓았던 부분입니다. "서버 배포했는데 왜 예전 화면이 나오죠?"
[11:00] 개발자: style.css 수정 → 서버 배포 완료
[11:01] 사용자: "아직도 옛날 스타일인데요?"
[11:02] 개발자: "제 브라우저에선 잘 나오는데요?" (로컬 서버 보고 있음 🤦)
[11:03] 사용자: "Ctrl+F5 눌러도 그대로예요"
[11:05] 개발자: "CDN 캐시 때문이네... 어떻게 지우지?"
# API로 캐시 전체 삭제
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
AWS CloudFront 예시:
# 특정 파일 무효화
aws cloudfront create-invalidation \
--distribution-id E1234567890ABC \
--paths "/css/*" "/js/*" "/images/logo.png"
문제점:
Before: /static/style.css
After: /static/style.v2.css
또는 /static/style.a8b3c9.css (Hash)
CDN은 다른 파일로 인식하므로 캐시를 안 찾습니다 → 즉시 반영
Webpack/Vite 자동화:// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js', // main.a8b3c9f2.js
chunkFilename: '[name].[contenthash].js',
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css', // style.b4d8e1a3.css
}),
],
}
결과:
<!-- 빌드 전 -->
<link rel="stylesheet" href="/static/style.css">
<!-- 빌드 후 -->
<link rel="stylesheet" href="/static/style.b4d8e1a3.css">
매번 빌드할 때마다 파일명이 바뀌므로:
# nginx 설정
# 1. HTML: 절대 캐싱 안 함 (항상 최신 확인)
location ~ \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
add_header Pragma "no-cache";
add_header Expires 0;
}
# 2. CSS/JS (해시 없는 버전) - 짧게 캐싱
location ~ \.(css|js)$ {
add_header Cache-Control "public, max-age=3600"; # 1시간
}
# 3. 이미지 - 길게 캐싱
location ~ \.(jpg|jpeg|png|gif|webp)$ {
add_header Cache-Control "public, max-age=31536000, immutable"; # 1년
}
# 4. 해시 포함 파일 - 영구 캐싱 (파일명이 바뀌면 다른 파일이니까)
location ~ \.(css|js)\?v= {
add_header Cache-Control "public, max-age=31536000, immutable";
}
헤더 설명:
| 헤더 | 의미 | 캐싱 위치 |
|---|---|---|
public | 누구나 캐싱 가능 | CDN, 프록시, 브라우저 |
private | 브라우저만 캐싱 | 브라우저만 |
no-cache | 캐싱하되, 매번 서버에 확인 | 조건부 캐싱 |
no-store | 절대 캐싱 금지 | 없음 (보안 중요 데이터) |
max-age=3600 | 3600초(1시간) 동안 유효 | 브라우저 + CDN |
s-maxage=3600 | CDN용 TTL (max-age 오버라이드) | CDN만 |
immutable | 절대 안 바뀜 (재검증 불필요) | 브라우저 |
// Express.js 서버
app.get('/api/user', (req, res) => {
res.set({
'Cache-Control': 'private, max-age=300', // 브라우저만 5분 캐싱
'Vary': 'Authorization', // 인증 헤더별로 다른 캐시
})
res.json({ name: 'John' })
})
app.get('/static/logo.png', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=31536000, immutable', # CDN+브라우저 1년
})
res.sendFile('logo.png')
})
1. HTML 파일: no-cache (항상 최신)
2. CSS/JS: 파일명에 해시 포함 + immutable (영구 캐싱)
3. 이미지: 1년 캐싱 (잘 안 바뀜)
4. API: private + 짧은 TTL (사용자별 다름)
결과:
클라우드 비용의 60~80%는 Egress(외부로 나가는 트래픽) 비용입니다.
AWS 월 청구서
────────────────────────────────
EC2 t3.medium $30
RDS db.t3.small $25
Data Transfer Out $740 ← 😱
────────────────────────────────
Total $795
트래픽이 늘어나자 서버보다 대역폭 비용이 25배 비쌌습니다.
Data Transfer OUT from EC2 to Internet:
First 10 TB/month: $0.09 per GB
Next 40 TB/month: $0.085 per GB
Next 100 TB/month: $0.07 per GB
Over 150 TB/month: $0.05 per GB
제 서비스는 월 8TB 전송:
Data Transfer OUT from CloudFront:
First 10 TB/month: $0.085 per GB
Next 40 TB/month: $0.080 per GB
Next 100 TB/month: $0.060 per GB
약간 싸지만... 진짜 마법은 다음입니다.
[EC2] → [CloudFront] : 무료 (같은 리전이면)
[CloudFront] → [사용자] : $0.085/GB
vs
[EC2] → [사용자] : $0.09/GB
핵심: 오리진(EC2)에서 CloudFront로 보낼 때는 무료니까, Cache Hit Rate이 높으면 엄청난 비용 절감!
제 경우:월 트래픽: 8 TB
Cache Hit Rate: 95%
Origin → CDN: 0.4 TB (5% Miss) → 무료
CDN → 사용자: 8 TB → $0.085 × 8,000 = $680
하지만!
Origin 트래픽이 줄어서 EC2 네트워크도 작은 인스턴스로 가능
EC2 t3.medium → t3.small ($30 → $17)
최종 비용:
CloudFront: $680
EC2: $17
RDS: $25
────────────
Total: $722
추가 최적화 (압축):
Gzip/Brotli 압축으로 트래픽 40% 감소
8TB → 4.8TB
$680 → $408
최종: $450
$795 → $450, 43% 절감
Cloudflare는 특정 스토리지 파트너와 Egress 비용 0원 정책을 만들었습니다.
파트너 목록:일반적인 구조:
[S3] → [CloudFront] → [사용자]
무료 $0.085/GB
Bandwidth Alliance:
[Backblaze B2] → [Cloudflare CDN] → [사용자]
무료 무료 (!)
실제 비용 비교 (월 10TB 전송):
AWS S3 + CloudFront:
- S3 저장: $23 (1TB)
- CDN 전송: $850 (10TB × $0.085)
- 합계: $873
Backblaze B2 + Cloudflare:
- B2 저장: $5 (1TB)
- CDN 전송: $0 (Bandwidth Alliance)
- 합계: $5 (!)
174배 차이
저는 이걸 알고 즉시 마이그레이션했습니다. 월 비용이 $450 → $45로 떨어졌습니다.
// 1. 이미지 최적화
// Before: 2MB JPEG
// After: 300KB WebP (압축 + 포맷 변환)
// Cloudflare Polish 또는 직접 구현
app.get('/images/:name', async (req, res) => {
const acceptsWebP = req.headers.accept?.includes('image/webp')
if (acceptsWebP) {
const webpPath = `${req.params.name}.webp`
res.sendFile(webpPath) // 85% 작음
} else {
res.sendFile(req.params.name) // 원본 JPEG
}
})
// 2. Gzip/Brotli 압축
// nginx에서 자동 처리
gzip on;
gzip_types text/css application/javascript application/json;
gzip_min_length 1000;
# Brotli (더 강력, 최신 브라우저)
brotli on;
brotli_types text/css application/javascript;
// 3. 불필요한 데이터 제거
// API 응답에서 null 필드 제거
const response = {
id: 123,
name: "John",
avatar: null, // 제거 대상
bio: null, // 제거 대상
}
// JSON.stringify로 null 제외
res.json(
JSON.parse(JSON.stringify(response, (k, v) => v === null ? undefined : v))
)
// 4. 리사이즈된 이미지 제공
// 2000×2000 원본을 모바일에도 보내지 말 것
<img
src="image-400.jpg" # 모바일
srcset="image-400.jpg 400w, image-800.jpg 800w, image-1200.jpg 1200w"
sizes="(max-width: 600px) 400px, (max-width: 1200px) 800px, 1200px"
/>
결국 이거였다: "어디에 저장하고 어디로 쏘느냐"가 클라우드 비용의 90%를 결정한다.
엣지에서 돌아가는 코드가 점점 복잡해지고 있습니다.
현재:// Cloudflare Workers AI
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const ai = new Ai(env.AI)
const response = await ai.run('@cf/meta/llama-2-7b-chat-int8', {
prompt: "서울에서 부산까지 가는 방법은?"
})
return new Response(JSON.stringify(response))
}
// 미국 오리진까지 안 가고 서울 엣지에서 AI 실행
// 지연 시간: 200ms → 15ms
WebSocket, Server-Sent Events(SSE) 같은 실시간 연결도 CDN을 거치게 되었습니다.
// Cloudflare Durable Objects = Stateful Edge Computing
export class ChatRoom {
constructor(state, env) {
this.state = state
this.sessions = []
}
async fetch(request) {
// WebSocket 연결
const [client, server] = Object.values(new WebSocketPair())
this.sessions.push(server)
server.addEventListener('message', event => {
// 같은 엣지 서버에 있는 다른 사용자들에게 즉시 브로드캐스트
this.sessions.forEach(session => {
session.send(event.data)
})
})
return new Response(null, { status: 101, webSocket: client })
}
}
// 한국 사용자들끼리 채팅 → 서울 엣지에서 처리
// 미국까지 왕복 불필요 → 지연 4ms
GDPR, 개인정보보호법 강화로 로그도 지역별로 격리하는 추세입니다.
기존:
- 한국 사용자 로그 → 미국 본사로 전송 → GDPR 위반 가능
새 방식:
- 한국 사용자 로그 → 한국 엣지에만 저장
- EU 사용자 로그 → EU 엣지에만 저장
- 리전 간 전송 금지
Cloudflare는 이미 Regional Services 옵션을 제공합니다.
A: 예전엔 그랬지만, 지금은 동적 컨텐츠도 가속합니다.
무료 ($0/월):
✅ 무제한 대역폭
✅ 기본 DDoS 방어
✅ SSL/TLS
✅ Anycast DNS
❌ 이미지 자동 최적화
❌ 상세 WAF 규칙
❌ 중국 가속
Pro ($20/월):
✅ 위 모든 기능
✅ Image Optimization (Polish)
✅ Mobile Redirect
✅ WAF 규칙 5개
Business ($200/월):
✅ WAF 규칙 무제한
✅ 우선 지원
✅ PCI 준수
Enterprise (가격 협상):
✅ 중국 네트워크
✅ 전담 팀
✅ SLA 보장
개인/스타트업은 무료로도 충분합니다.
전 세계 사용자 → 필수
한국만 → 선택
대용량 파일 (이미지, 비디오) → 강력 추천
1. Cloudflare 가입
2. 도메인 추가 (example.com)
3. DNS 네임서버 변경 (도메인 등록 업체에서)
- 기존: ns1.godaddy.com
- 새로: chad.ns.cloudflare.com
4. 끝! (자동으로 CDN 활성화)
DNS가 전파되면 (5분~48시간) 모든 트래픽이 Cloudflare를 거칩니다.
AWS CloudFront (좀 더 복잡):1. CloudFront Distribution 생성
2. Origin 설정 (EC2, S3 등)
3. Behavior 설정 (캐싱 규칙)
4. DNS에 CNAME 추가
- d1234abcd.cloudfront.net → cdn.example.com
첫 요청 (Cold Start): Cache Miss라 오리진까지 왕복 → 느림
지역에 PoP 없음: 가까운 엣지가 없으면 효과 반감
잘못된 캐싱 설정: 캐싱하면 안 되는 걸 캐싱 → 오히려 문제
일반 스타트업은 단일 CDN으로 충분합니다.
3년 전 제가 처음 글로벌 서비스를 만들 때, 저는 "좋은 코드가 빠른 서비스를 만든다"고 믿었습니다.
틀렸습니다.
빠른 서비스는 물리학과의 싸움입니다.
CDN은 단순한 캐싱 서버가 아니라, 지리적 한계를 극복하는 인프라였습니다.
제가 이해한 핵심:
결국 이거였다: 현대 웹은 CDN 없이 불가능하다.
넷플릭스, 유튜브, 페이스북... 우리가 쓰는 모든 서비스 뒤에는 CDN이 있습니다. 그게 보이지 않을 뿐이죠.
이제 여러분도 압니다. 버퍼링 없는 스트리밍, 빠른 웹사이트, 끊기지 않는 서비스... 그 모든 것의 비밀을.
When I first launched a global web service, I hosted it on AWS us-east-1 (Virginia). My American friend said, "Wow, this is fast!" Meanwhile, users in Korea complained that a single image took 3 seconds to load.
I thought, "Isn't light supposed to be fast? 300,000 km/s?"
Wrong.
The Reality Check:I was furious. Not at my server, not at my code, but at physics itself. No amount of optimization can beat the speed of light.
That's when I realized: "Don't make the server faster. Bring the server closer."
This physical limitation is what CDN (Content Delivery Network) solves. It's not about speed - it's about distance.
In the late 1990s, the web was so slow that people sarcastically called it the "World Wide Wait" instead of "World Wide Web."
The Problem:MIT math professor Tom Leighton and his student Danny Lewin approached this mathematically:
They founded Akamai Technologies (Hawaiian for "smart"), the first commercial CDN.
Today: Over 50% of global internet traffic flows through CDNs like Cloudflare, AWS CloudFront, Fastly, and Akamai. Netflix, YouTube, and Facebook would be impossible without them.
Cloudflare alone operates in 310+ cities worldwide. Right now, there's probably a Cloudflare server within 10km of you.
Think of it like a franchise coffee shop:
Origin Server (Headquarters): The main server with the "real" data
Edge Server (Branch): Thousands of cache servers worldwide
# User requests example.com/logo.png
# Browser does DNS lookup first
$ dig example.com
;; ANSWER SECTION:
example.com. 60 IN A 104.16.132.229 # Cloudflare Anycast IP
Here's the magic: This IP isn't one server. Thousands of servers share the same IP (Anycast).
BGP routing automatically connects you to the nearest server:
[Seoul Edge Server Log]
2025-05-19 14:32:15 KST
Request: GET /logo.png
Cache Status: MISS (first request, not in cache)
Action: Fetching from Origin (us-east-1)
Origin Response Time: 285ms
Saved to Cache with TTL: 86400s (24 hours)
Response to Client: 320ms total
Step 3: Subsequent Requests Are Fast
[Seoul Edge Server Log]
2025-05-19 14:32:18 KST
Request: GET /logo.png
Cache Status: HIT (found in cache!)
Response Time: 4ms
285ms → 4ms = 70x faster
CDN performance is measured by Cache Hit Rate:
Cache Hit Rate = (Cache Hits / Total Requests) × 100%
Tuning cache strategies well can push Hit Rate from 60% to 95%. There are many reported cases where this kind of improvement leads to dramatic reductions in server costs and response times.
This is crucial for distributed systems interviews. I didn't understand its importance until I experienced the disaster firsthand.
You have 100 Edge servers. Where do you store logo.png?
def get_server(key):
server_id = hash(key) % 100 # 0-99
return f"edge-{server_id}"
get_server("logo.png") # edge-42
get_server("video.mp4") # edge-87
Looks fine, right? But then...
Add 1 Server:def get_server(key):
server_id = hash(key) % 101 # now 101 servers
get_server("logo.png") # edge-43 (changed!)
get_server("video.mp4") # edge-88 (changed!)
Result: All keys remap → Cache Stampede → Origin server dies.
Real scenario I experienced:
The breakthrough: "Adding/removing servers only affects a small portion of keys."
Concept: Ring Structureimport hashlib
class ConsistentHash:
def __init__(self):
self.ring = {} # {hash_value: server_name}
self.sorted_keys = []
def add_server(self, server_name):
# Place server on ring (with virtual nodes)
for i in range(150): # 150 replicas
virtual_key = f"{server_name}:{i}"
hash_val = int(hashlib.md5(virtual_key.encode()).hexdigest(), 16)
self.ring[hash_val] = server_name
self.sorted_keys = sorted(self.ring.keys())
def get_server(self, key):
hash_val = int(hashlib.md5(key.encode()).hexdigest(), 16)
# Find first server clockwise
for ring_key in self.sorted_keys:
if hash_val <= ring_key:
return self.ring[ring_key]
# Wrap around
return self.ring[self.sorted_keys[0]]
# Test
ch = ConsistentHash()
ch.add_server("edge-seoul")
ch.add_server("edge-tokyo")
ch.add_server("edge-osaka")
print(ch.get_server("logo.png")) # edge-tokyo
print(ch.get_server("video.mp4")) # edge-seoul
# Add server
ch.add_server("edge-busan")
print(ch.get_server("logo.png")) # edge-tokyo (unchanged!)
print(ch.get_server("video.mp4")) # edge-seoul (unchanged!)
Key Points:
Virtual Nodes: Each server appears 150 times on the ring
Adding servers: Only ~1% of keys remap (not 100%!)
Removing servers: Only that server's keys redistribute
This is also how AWS DynamoDB handles partition distribution at petabyte scale.
CDNs have evolved from static file servers to code execution platforms.
[User] "Resize this image to 400x300"
↓
[Edge] "Can't do that, forwarding to Origin"
↓ (200ms)
[Origin] Resizes image → Response
↓ (200ms)
[User] Receives (400ms+ total)
[User] "Resize this image to 400x300"
↓
[Edge] Executes V8 JavaScript → Resizes → Response
↓ (15ms)
[User] Receives (15ms total)
26x faster
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const url = new URL(request.url)
// 1. A/B Testing at the Edge
const variant = Math.random() < 0.5 ? 'A' : 'B'
// 2. Geo-blocking
const country = request.cf.country
if (country === 'CN') {
return new Response('Not available', { status: 403 })
}
// 3. Image Resizing
if (url.pathname.endsWith('.jpg')) {
const imageRequest = new Request(url, {
cf: { image: { width: 400, quality: 85 } }
})
return fetch(imageRequest)
}
// 4. Custom Cache Key
const cacheKey = `${url.pathname}:${request.headers.get('Accept-Language')}`
return fetch(new Request(url, { cf: { cacheKey } }))
}
Use Cases:
I used this to reduce Origin requests by 60%.
When you watch a 4K movie on Netflix, you're not downloading a 15GB file. That would take an hour to start.
[Original Movie: 2 hours = 15GB]
↓
[Transcoding Server]
↓
Split into 3,600 × 2-second chunks:
- chunk_0000.ts (1080p) - 4MB
- chunk_0000.ts (720p) - 2MB
- chunk_0000.ts (480p) - 1MB
- chunk_0000.ts (360p) - 0.5MB
...
↓
[Upload all to CDN]
Master Playlist (master.m3u8):
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=8000000,RESOLUTION=1920x1080
1080p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=4000000,RESOLUTION=1280x720
720p/playlist.m3u8
The client measures bandwidth in real-time and switches quality:
class HLSPlayer {
async playVideo() {
let currentQuality = '720p' // Start conservatively
while (true) {
const startTime = Date.now()
const chunk = await fetch(currentQuality.nextChunk)
const downloadTime = Date.now() - startTime
// Measure bandwidth
const bandwidth = (chunk.size * 8) / (downloadTime / 1000)
// Adjust quality
if (bufferHealth < 5 && bandwidth < currentQuality.bandwidth) {
currentQuality = selectLowerQuality() // Downgrade
} else if (bufferHealth > 20 && bandwidth > currentQuality.bandwidth * 1.5) {
currentQuality = selectHigherQuality() // Upgrade
}
appendToBuffer(chunk)
await sleep(2000)
}
}
}
Timeline:
[00:00] Start → 720p (safe choice)
[00:10] Bandwidth 8Mbps → Upgrade to 1080p
[00:45] Enter subway, 1.5Mbps → Downgrade to 480p
[01:12] WiFi connected, 12Mbps → Back to 1080p
No buffering, seamless experience.
All these chunks (thousands of them) are cached at Edge servers:
[Korean User A] → chunk_0042.ts → [Seoul Edge] Hit! → 4ms
[Korean User B] → chunk_0042.ts → [Seoul Edge] Hit! → 4ms
[Korean User Z] → chunk_0042.ts → [Seoul Edge] Hit! → 4ms
Without CDN (direct from US Origin):
With CDN: Watch a movie from the other side of the planet as if it's local.
CDN also serves as a critical security layer.
[AWS CloudWatch]
🚨 EC2 CPU 100%
🚨 Network In: 15 Gbps (normal: 0.2 Gbps)
🚨 Server Unreachable
When a DDoS attack hits, a small server (1Gbps network) getting flooded with 15Gbps traffic has no chance.
Result: Server dead. Legitimate users blocked.
[Attacker] 100 Gbps attack
↓
[CDN Global Network]
Total capacity: 200 Tbps (200,000 Gbps)
310 cities, thousands of servers
↓
Seoul Edge: 3 Gbps (manageable)
Tokyo Edge: 5 Gbps (manageable)
London Edge: 2 Gbps (manageable)
...
↓
[Origin] Only legitimate traffic reaches here
2) Anycast Distribution
[Attacker in China] → Attack → [Nearest CDN = Hong Kong Edge]
[Attacker in Russia] → Attack → [Nearest CDN = Moscow Edge]
[Attacker in Brazil] → Attack → [Nearest CDN = São Paulo Edge]
# Attack traffic automatically distributed globally
# Each Edge receives manageable portion
3) Pattern-based Blocking
// Cloudflare Firewall Rules
// Rate limiting
if (request.countPerSecond(request.ip) > 100) {
return { action: 'block', reason: 'rate_limit' }
}
// Bot detection
if (/curl|bot|python/i.test(request.userAgent)) {
return { action: 'challenge' } // CAPTCHA
}
// SQL Injection detection
if (/union.*select|drop.*table/i.test(request.url)) {
return { action: 'block', reason: 'sql_injection' }
}
GitHub's own servers couldn't handle it. CDN's global capacity was essential.
June 8, 2021. I tried to access Amazon - down. Reddit - down. Twitch - down. CNN - down.
"Is my WiFi broken?" Nope. The entire internet was broken.
Affected sites:
Not a hack. Not hardware failure.
Timeline:[05:00 UTC] Customer updates service config
↓
[Config] Added regex pattern to Varnish VCL
↓
[05:47 UTC] Config deployed globally
↓
[Bug] Certain HTTP headers trigger regex infinite loop (ReDoS)
↓
[Result] CPU 100% → Servers hang → 85% of Edge servers down
↓
[05:58 UTC] Fastly emergency rollback
↓
[06:44 UTC] Service restored
The problematic regex (estimated):
/(\?|&)([^=]+)=([^&]*)/
# With certain input: "?a=1&&&&&&&&&&&&&&&&&&&&&&&&"
# → Catastrophic backtracking → CPU freeze (ReDoS)
One CDN dependency = no backup when it fails.
Solution: Multi-CDNupstream cdn_pool {
server cdn1.cloudflare.com:443 weight=3;
server cdn2.fastly.com:443 weight=2;
server cdn3.akamai.com:443 backup;
}
server {
location /static/ {
proxy_pass https://cdn_pool;
proxy_next_upstream error timeout http_503;
}
}
2) Config Changes Are Code
One config change crashed the internet.
Best Practice: Canary Deploymentdeployment:
strategy: canary
stages:
- percent: 1
duration: 10m
error_threshold: 0.1% # Abort if >0.1% errors
- percent: 10
duration: 30m
- percent: 100
auto_rollback: true
Phil Karlton said:
"There are only two hard things in Computer Science: cache invalidation and naming things."
My biggest headache: "Deployed new code but users see old version!"
# Cloudflare API
curl -X POST "https://api.cloudflare.com/client/v4/zones/{id}/purge_cache" \
-H "Authorization: Bearer {token}" \
--data '{"purge_everything":true}'
Problems:
Before: /static/style.css
After: /static/style.a8b3c9.css (hash)
CDN sees it as a different file → instant update, zero cost.
Webpack automation:// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js', // main.a8b3c9.js
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
}),
],
}
Every build creates new filenames → No purge needed.
# HTML: Never cache
location ~ \.html$ {
add_header Cache-Control "no-cache, no-store, must-revalidate";
}
# CSS/JS with hash: Cache forever
location ~ \.(css|js)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
# Images: 1 year
location ~ \.(jpg|png|webp)$ {
add_header Cache-Control "public, max-age=31536000";
}
Headers explained:
| Header | Meaning | Cached Where |
|---|---|---|
public | Anyone can cache | CDN, proxies, browsers |
private | Browser only | Browser |
no-cache | Revalidate every time | Conditional caching |
no-store | Never cache | Nowhere (sensitive data) |
max-age=3600 | Valid for 1 hour | Browser + CDN |
s-maxage=3600 | CDN TTL (overrides max-age) | CDN only |
immutable | Never changes | Browser |
My strategy:
no-cacheimmutableprivate + short TTLResult: 95% Hit Rate, instant deployments, significant cost reduction (the higher the Hit Rate, the less you pay for origin traffic).
Cloud costs = 60-80% Egress (outbound traffic).
AWS Monthly Bill
────────────────────────
EC2 t3.medium $30
RDS db.t3.small $25
Data Transfer OUT $740 ← 😱
────────────────────────
Total $795
Bandwidth cost 25x more than servers.
EC2 to Internet:
First 10 TB: $0.09/GB
Next 40 TB: $0.085/GB
Next 100 TB: $0.07/GB
My service: 8TB/month
CloudFront to Internet:
First 10 TB: $0.085/GB
Slightly cheaper, but the real magic:
[EC2] → [CloudFront]: FREE (same region)
[CloudFront] → [Users]: $0.085/GB
With 95% Cache Hit Rate:
Monthly traffic: 8 TB
Cache Hit: 95%
Origin → CDN: 0.4 TB (5% Miss) → FREE
CDN → Users: 8 TB → $680
Plus:
Reduced Origin traffic → Smaller EC2 instance
t3.medium → t3.small: $30 → $17
Final cost:
CloudFront: $680
EC2: $17
RDS: $25
──────────
Total: $722
With compression (40% reduction):
8TB → 4.8TB
$722 → $450
$795 → $450 = 43% savings
Cloudflare + certain storage partners = $0 egress.
Partners:AWS S3 + CloudFront:
- S3 storage: $23 (1TB)
- CDN transfer: $850 (10TB)
- Total: $873
Backblaze B2 + Cloudflare:
- B2 storage: $5 (1TB)
- CDN transfer: $0 (Bandwidth Alliance)
- Total: $5 (!)
174x cheaper
I migrated immediately. Monthly cost: $450 → $45.
Code running at the Edge is getting more complex:
Current:// AI at the Edge
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const ai = new Ai(env.AI)
const response = await ai.run('@cf/meta/llama-2-7b-chat', {
prompt: "How to get from Seoul to Busan?"
})
return new Response(JSON.stringify(response))
}
// AI runs in Seoul Edge, not US Origin
// Latency: 200ms → 15ms
WebSocket, SSE (Server-Sent Events) now go through CDN:
// Cloudflare Durable Objects
export class ChatRoom {
async fetch(request) {
const [client, server] = Object.values(new WebSocketPair())
this.sessions.push(server)
server.addEventListener('message', event => {
// Broadcast to all users on same Edge server
this.sessions.forEach(s => s.send(event.data))
})
return new Response(null, { status: 101, webSocket: client })
}
}
// Korean users chatting → Seoul Edge handles it
// No round-trip to US → 4ms latency
GDPR compliance: Logs stay in their region.
Old:
- Korean user logs → Sent to US → GDPR violation
New:
- Korean user logs → Korean Edge only
- EU user logs → EU Edge only
- No cross-region transfer
Cloudflare already offers Regional Services.