콘텐츠로 이동

DNS 심화 & CDN

전제 지식: L2 dns-basics.mdx에서 DNS 재귀/반복 조회, A/CNAME/MX/TXT 레코드, 기본 TTL을 다뤘다. L2 http-cache.mdx에서 HTTP 캐시 헤더를, L7 tls-https.mdx에서 인증서 체인과 TLS 채널을, L7 http2-http3.mdx에서 HTTP/2·3 전송 경계를 다뤘다. 이 문서는 그 위에서 “이름을 믿을 수 있는가”, “조회 내용을 누가 볼 수 있는가”, “원본 서버까지 매번 가야 하는가”를 분리해 읽는다.

첫 회독 기준: 처음에는 특정 공급자 콘솔이나 CLI 절차를 외우지 않는다. DNSSEC, DoH/DoT, TTL, PoP, origin, cache key, invalidation, stale-if-error가 어떤 문제를 줄이는지 설명할 수 있으면 된다. Route 53과 CloudFront는 본문에서 AWS 예시로 다루지만, 핵심 원리는 다른 DNS/CDN에도 그대로 적용된다. AWS 용어는 공급자 예시일 뿐이며, 같은 절을 읽을 때는 “권한 DNS”, “CDN edge”, “cache policy”, “origin” 같은 일반 모델로 번역해도 된다.


0. 선행 기술의 한계와 등장 이유 — 이 토픽의 핵심 질문

섹션 제목: “0. 선행 기술의 한계와 등장 이유 — 이 토픽의 핵심 질문”

DNS와 CDN은 둘 다 “사용자 요청을 어디로 보낼 것인가”를 다룬다. 하지만 두 기술이 해결하는 문제는 다르다.

DNS는 이름을 주소로 바꾸는 계층이다. 사용자는 www.example.com을 입력하지만 실제 네트워크는 IP 주소로 패킷을 보낸다. 이때 중요한 질문은 세 가지다.

1. 이 DNS 응답이 진짜인가?
2. 이 DNS 질의를 누가 볼 수 있는가?
3. 같은 이름을 어떤 서버로 안내할 것인가?

CDN(Content Delivery Network)은 원본 서버(origin) 앞에 전 세계 캐시 계층을 둔다. 사용자가 매번 먼 origin까지 가지 않고 가까운 edge에서 응답받게 만드는 구조다. 이때 중요한 질문은 다시 세 가지다.

1. 어떤 응답을 edge에 저장할 것인가?
2. 같은 응답이라고 판단하는 기준은 무엇인가?
3. origin이 느리거나 죽었을 때 stale 응답을 허용할 것인가?

이 문서를 잘 읽는 방법은 제품 이름보다 경계를 먼저 보는 것이다. DNSSEC는 DNS 응답의 진위를 다룬다. DoH/DoT는 DNS 질의의 전송 프라이버시를 다룬다. CDN 캐시는 HTTP 응답의 거리와 재사용을 다룬다. 엣지 컴퓨팅은 캐시 앞뒤에서 실행할 수 있는 작은 로직의 위치를 다룬다.

DNS/CDN이 줄이는 실패

위조 응답

DNSSEC(DNS Security Extensions)가 RRSIG·DNSKEY·DS로 응답의 서명 체인을 검증한다.

Cache poisoning, 잘못된 NS 위임, KSK/DS 불일치가 의심될 때

질의 노출

DoT(DNS over TLS)와 DoH(DNS over HTTPS)가 DNS 메시지를 TLS 안으로 넣는다.

ISP, 공용 Wi-Fi, 사내 네트워크 장비가 DNS 질의를 읽는 경계를 줄이고 싶을 때

원거리 origin 지연

CDN edge cache가 사용자 가까운 PoP(Point of Presence)에서 응답한다.

정적 자산, 공개 HTML, 읽기 많은 API가 매번 origin RTT를 치르는 것이 비쌀 때

origin 장애

stale-if-error, origin failover, DNS failover가 에러 대신 제한된 구버전 응답 또는 대체 origin을 사용한다.

정합성보다 가용성이 우선인 공개 콘텐츠에서 장애 전파를 줄이고 싶을 때

0-1. 수치 감각: 왜 origin-only가 쉽게 느려지는가

섹션 제목: “0-1. 수치 감각: 왜 origin-only가 쉽게 느려지는가”

서울 사용자가 미국 동부 origin에 직접 연결한다고 가정하자. 왕복 지연시간(RTT)이 150ms라면, TCP 연결과 TLS 핸드셰이크만으로도 여러 번의 왕복이 필요하다. HTTP/2나 HTTP/3가 연결 재사용을 잘해도 첫 연결에는 여전히 네트워크 거리가 보인다.

origin-only 첫 요청의 감각
DNS 조회: 10~50ms
TCP/TLS 준비: RTT 1~3회
origin 처리: 50~200ms
응답 첫 바이트: 쉽게 500ms 이상

CDN이 가까운 edge에서 캐시 히트(hit)를 내면 그림이 바뀐다. 사용자는 서울 또는 가까운 지역의 PoP까지 왕복하고, origin 왕복은 사라진다. 캐시 미스(miss)일 때만 edge가 origin으로 간다.

edge cache hit
사용자 -> 가까운 edge: 수 ms~수십 ms
edge -> origin: 없음
origin 처리: 없음
응답 첫 바이트: cache lookup + 가까운 RTT

이 숫자는 CDN이 “서버를 빠르게 만든다”는 뜻이 아니다. CDN은 원본 계산을 없애는 것이 아니라, 이미 계산된 공개 응답을 사용자 가까이에 재사용한다. 따라서 cache hit ratio와 cache key 설계가 CDN 성능의 핵심이다.

DNS/CDN을 배울 때 자주 섞이는 경계가 있다. 아래 세 문장은 서로 대체되지 않는다.

DNSSEC가 있다
-> DNS 응답이 권한 있는 존의 개인키로 서명됐는지 검증할 수 있다.
-> 질의 내용이 숨겨지는 것은 아니다.
DoH/DoT를 쓴다
-> 클라이언트와 recursive resolver 사이의 DNS 메시지가 TLS로 보호된다.
-> resolver 자체를 신뢰해야 하는 문제는 남는다.
HTTPS를 쓴다
-> HTTP 요청/응답이 TLS 채널로 보호되고 서버 인증서를 검증한다.
-> DNS 응답이 위조되지 않았다는 보장과는 별도다.

예를 들어 DoH를 쓰면서 DNSSEC 검증을 하지 않으면, 질의는 암호화되어도 resolver가 받은 응답의 진위를 검증하지 않을 수 있다. 반대로 DNSSEC를 쓰면서 전통 DNS를 쓰면, 응답 위조는 검출할 수 있어도 질의 이름은 네트워크에서 보일 수 있다.

퀴즈

DNSSEC와 DoH/DoT는 같은 보안 문제를 푸는가?

힌트: 서명과 암호화의 차이를 먼저 나눈다.

정답 보기

아니다. DNSSEC는 DNS 응답이 권한 있는 존에서 온 것인지 서명 체인으로 검증한다. DoH/DoT는 클라이언트와 resolver 사이의 DNS 메시지를 TLS로 감싸 질의 노출과 중간 조작을 줄인다. 응답 진위와 전송 프라이버시는 별도 경계다.


1. DNSSEC — DNS 응답에 서명을 붙인다

섹션 제목: “1. DNSSEC — DNS 응답에 서명을 붙인다”

DNSSEC(DNS Security Extensions)는 DNS 응답에 디지털 서명을 붙여 응답의 진위와 무결성을 검증하게 하는 확장이다. 암호화가 아니라 서명이다. 즉, example.com A 93.184.216.34라는 응답의 내용은 보일 수 있지만, 그 응답이 권한 있는 존에서 서명한 것인지 확인할 수 있다.

DNSSEC가 풀려는 대표 문제는 DNS cache poisoning이다. 공격자가 recursive resolver 캐시에 가짜 응답을 넣으면, 이후 사용자들은 정상 도메인을 공격자 IP로 해석할 수 있다.

DNSSEC 없음
공격자: bank.example A 10.1.2.3 응답을 resolver 캐시에 주입
resolver: 진위 검증 수단이 약함
사용자: bank.example -> 10.1.2.3으로 접속

DNSSEC가 있으면 공격자는 가짜 A 레코드만 만들어서는 충분하지 않다. 해당 RRset(Resource Record Set)에 맞는 유효한 RRSIG를 만들어야 한다. 개인키가 없으면 그 서명을 만들 수 없으므로 resolver는 SERVFAIL 같은 실패로 응답을 버린다.

DNSSEC 있음
공격자: bank.example A 10.1.2.3 + 가짜 RRSIG 주입
resolver: DNSKEY로 RRSIG 검증
결과: 서명 불일치 -> 응답 폐기

DNSSEC는 약어가 많다. 처음에는 아래 다섯 단어만 붙잡으면 된다.

용어풀어 쓰기역할
RRsetResource Record Set같은 이름·타입·클래스의 DNS 레코드 묶음. DNSSEC는 보통 RRset 단위로 서명한다.
RRSIGResource Record SignatureRRset에 대한 디지털 서명. “이 레코드 묶음이 변조되지 않았다”를 검증한다.
DNSKEYDNS Key존(zone)의 공개키. resolver가 RRSIG를 검증할 때 쓴다.
DSDelegation Signer부모 존에 저장되는 자식 존 KSK의 해시. 부모와 자식 신뢰를 연결한다.
chain of trust신뢰 체인루트부터 TLD, 도메인 존까지 DS와 DNSKEY를 따라 검증하는 구조.

ZSK(Zone Signing Key)는 실제 A, CNAME, MX 같은 RRset을 서명하는 키다. KSK(Key Signing Key)는 DNSKEY RRset, 특히 ZSK를 포함한 키 묶음을 서명하고, 그 해시가 부모 존의 DS 레코드로 올라간다.

ZSK
- 레코드 서명에 자주 쓰인다.
- 비교적 자주 교체할 수 있다.
KSK
- DNSKEY RRset과 신뢰 위임을 지탱한다.
- 부모 존의 DS와 연결되므로 교체 절차가 더 조심스럽다.

DNSSEC의 신뢰는 “이 서버가 말했으니 믿는다”가 아니다. 루트 trust anchor에서 시작해 부모 존이 자식 존의 키를 가리키는 방식으로 내려간다.

Root(.)
DNSKEY: root KSK/ZSK
DS: .com KSK의 해시
.com
DNSKEY: .com KSK/ZSK
DS: example.com KSK의 해시
example.com
DNSKEY: example.com KSK/ZSK
A: www.example.com -> 93.184.216.34
RRSIG: A RRset에 대한 서명

resolver가 www.example.com A를 검증하는 흐름은 다음과 같다.

  1. www.example.com A RRset과 RRSIG를 받는다.
  2. example.com DNSKEY 중 ZSK로 RRSIG를 검증한다.
  3. 그 ZSK가 들어 있는 DNSKEY RRset이 KSK로 서명됐는지 확인한다.
  4. example.com KSK의 해시가 .com 존의 DS와 일치하는지 확인한다.
  5. .com DNSKEY도 루트의 DS와 이어지는지 확인한다.
  6. 루트 DNSKEY는 클라이언트 또는 resolver가 가진 trust anchor로 검증한다.

이 구조는 TLS 인증서 체인과 비슷해 보인다. 하지만 역할은 다르다. TLS 인증서 체인은 “이 서버 공개키가 이 호스트의 것인가”를 검증하고, DNSSEC 체인은 “이 DNS 응답이 이 존의 권한 있는 데이터인가”를 검증한다.

example.com의 A 레코드가 다음처럼 왔다고 하자.

example.com. 3600 IN A 93.184.216.34
example.com. 3600 IN RRSIG A 13 2 3600 (
20250215000000 20250115000000 12345 example.com.
abc123def456...
)

RRSIG 안에는 서명 대상 타입, 알고리즘, 원본 TTL, 서명 만료 시각, 서명 시작 시각, key tag, signer name, 실제 서명이 들어 있다. resolver는 key tag와 signer name을 보고 어떤 DNSKEY로 검증할지 찾는다.

검증 질문
1. 이 RRSIG가 A RRset과 맞는가?
2. 이 RRSIG를 만든 DNSKEY가 example.com 존의 키인가?
3. 그 DNSKEY의 KSK가 부모 .com 존의 DS와 이어지는가?
4. .com 역시 root trust anchor까지 이어지는가?

검증이 성공하면 DNS 응답 플래그에 AD(Authenticated Data)가 붙을 수 있다. AD는 recursive resolver가 DNSSEC 검증을 통과한 데이터를 돌려준다는 신호다. 반대로 DS와 DNSKEY가 어긋나거나 RRSIG가 만료되면 사용자는 보통 SERVFAIL을 본다.

Terminal window
# 관찰용 최소 명령
dig +dnssec cloudflare.com A
dig DS cloudflare.com
dig DNSKEY cloudflare.com

명령 자체를 외우는 것보다 관찰 포인트가 중요하다. ad 플래그가 있는지, RRSIG가 같이 오는지, DS와 DNSKEY가 부모-자식 관계로 이어지는지를 본다.

1-5. 부정적 응답도 서명해야 한다

섹션 제목: “1-5. 부정적 응답도 서명해야 한다”

DNSSEC는 “있는 레코드”만 서명하면 끝나지 않는다. 공격자가 “없는 이름”을 조작할 수도 있기 때문이다.

예를 들어 not-exist.example.com이 없다는 응답도 검증 가능해야 한다. 그렇지 않으면 공격자가 NXDOMAIN을 가짜로 만들거나, 반대로 존재하지 않는 이름이 존재한다고 속일 수 있다.

NSEC(Next Secure)는 정렬된 존 이름 사이의 범위를 보여주며 “이 사이에는 이름이 없다”를 증명한다. 문제는 NSEC 응답을 따라가면 존 안의 이름을 열거(zone walking)할 수 있다는 점이다.

NSEC3는 이름을 해시한 형태로 부정적 응답을 증명한다. 존 열거를 더 어렵게 만들지만, 짧고 예측 가능한 이름은 해시 사전 공격으로 추측될 수 있다.

NSEC
장점: 부정적 응답 증명이 단순하다.
경계: 존 이름 열거가 쉬워질 수 있다.
NSEC3
장점: 해시로 존 열거를 어렵게 한다.
경계: 완전한 비밀화가 아니라 추측 비용을 올리는 방식이다.

DNSSEC를 “응답 서명”으로만 외우면 부정적 응답을 놓친다. 실제 운영 장애에서는 “없는 이름이 안전하게 없음으로 검증되는가”도 중요하다.

DNSSEC 키 교체는 단순히 새 키로 바꾸고 이전 키를 지우는 작업이 아니다. DNS는 캐시가 있는 시스템이므로 resolver마다 이전 DNSKEY나 DS를 들고 있는 시간이 다르다.

잘못된 ZSK 교체
T0: 새 ZSK 생성
T0: 바로 새 ZSK로 서명
T0: 구 ZSK 삭제
문제:
어떤 resolver는 아직 구 DNSKEY만 캐시하고 있다.
새 RRSIG를 구 ZSK로 검증하려 한다.
서명 불일치 -> SERVFAIL

안전한 교체의 핵심은 중복 게시(pre-publish) 기간이다. 새 키를 먼저 공개해 resolver 캐시에 퍼지게 하고, 충분히 기다린 뒤 서명을 전환한다. 마지막으로 구 키를 제거한다.

ZSK pre-publish 감각
1. 새 ZSK를 DNSKEY RRset에 추가한다. 구 ZSK는 유지한다.
2. DNSKEY TTL 이상 기다린다.
3. 새 ZSK로 RRset 서명을 전환한다.
4. 다시 TTL 이상 기다린다.
5. 구 ZSK를 제거한다.

KSK는 더 조심스럽다. 부모 존의 DS 레코드와 맞아야 하기 때문이다. 새 KSK를 DNSKEY에 추가하고, 부모 존에 새 DS를 등록하고, DS TTL이 지난 뒤 구 KSK와 구 DS를 단계적으로 제거해야 한다.

실패 신호는 명확하다. 일부 resolver에서만 SERVFAIL이 나오고, 검증을 끄면 응답이 정상으로 보이면 DNSSEC 체인 문제를 먼저 의심한다.

Terminal window
# 검증 실패와 비검증 응답 비교
dig +dnssec example.com A @8.8.8.8
dig +cd example.com A @8.8.8.8

+cd는 checking disabled의 의미다. 검증을 끄면 A 레코드가 나오는데 검증을 켜면 SERVFAIL이면, 네트워크 단절보다 DNSSEC 서명·키·DS 불일치 가능성이 더 크다.


2. DoH / DoT — DNS 질의를 암호화한다

섹션 제목: “2. DoH / DoT — DNS 질의를 암호화한다”

DoT(DNS over TLS)는 DNS 메시지를 TLS 연결 위에 실어 보내는 방식이다. 보통 전용 포트 853을 쓴다. 기존 DNS wire format은 유지하고, 전송 계층에 TLS를 붙인다고 이해하면 된다.

DoH(DNS over HTTPS)는 DNS 메시지를 HTTPS 요청/응답으로 실어 보내는 방식이다. 보통 443 포트를 쓰며, HTTP/2 또는 HTTP/3 위에서 동작할 수 있다. 브라우저와 애플리케이션이 HTTPS 인프라를 통해 DNS 질의를 보낼 수 있다는 점이 특징이다.

전통 DNS
client -> UDP/TCP 53 -> resolver
질의 이름이 평문으로 보이기 쉽다.
DoT
client -> TLS 853 -> resolver
DNS 메시지는 TLS 안에 들어간다.
DoH
client -> HTTPS 443 -> resolver endpoint
DNS 메시지는 HTTPS 요청 안에 들어간다.

2-2. DoH/DoT가 가리는 것과 못 가리는 것

섹션 제목: “2-2. DoH/DoT가 가리는 것과 못 가리는 것”

DoH/DoT는 클라이언트와 recursive resolver 사이의 구간을 보호한다. 공용 Wi-Fi나 ISP가 사용자의 DNS 질의 이름을 직접 읽기 어렵게 만든다.

하지만 resolver 이후의 세계는 별도다. resolver는 여전히 사용자의 질의를 볼 수 있다. resolver가 authoritative server로 보내는 질의는 resolver의 정책과 구현에 따라 달라진다. 또한 접속한 서버의 IP, TLS SNI, 트래픽 크기와 타이밍 같은 메타데이터는 다른 계층의 문제다.

DoH/DoT가 줄이는 것
- 로컬 네트워크에서 DNS 질의 이름 노출
- UDP/53 조작 또는 단순 DNS hijacking
- 중간자의 응답 변조 가능성
DoH/DoT가 해결하지 않는 것
- resolver 자체에 대한 신뢰
- DNSSEC 검증 여부
- 접속 IP와 트래픽 패턴 노출
- HTTPS 인증서 검증 실패

따라서 프라이버시 질문은 “암호화했는가”에서 끝나지 않는다. 어떤 resolver를 신뢰할 것인지, 그 resolver가 로그를 어떻게 다루는지, 기업망에서 보안 모니터링과 충돌하는지까지 봐야 한다.

기준DoTDoH
기본 포트853443
프로토콜 모양DNS + TLSDNS + HTTPS
네트워크 식별성전용 포트라 식별이 쉽다일반 HTTPS와 섞이기 쉽다
운영 정책방화벽에서 허용/차단 경계가 명확하다애플리케이션 단위 우회가 쉬워 정책 충돌 가능성이 있다
구현 위치OS·공유 resolver 설정에 잘 맞는다브라우저·앱 내장 resolver에 잘 맞는다

개인 사용자의 프라이버시를 높이는 목적이라면 DoH가 편한 경우가 많다. 기업 환경에서 “어떤 DNS resolver를 쓰는지”를 명시적으로 관리하려면 DoT가 더 투명할 수 있다. 모바일 앱이 자체 DoH endpoint를 쓰면, 운영팀의 내부 DNS 정책과 보안 로그를 우회할 수 있다.

2-4. DNSSEC와 DoH/DoT를 함께 읽는 반례

섹션 제목: “2-4. DNSSEC와 DoH/DoT를 함께 읽는 반례”

다음 네 가지 조합을 보면 경계가 분명해진다.

조합질의 프라이버시응답 진위 검증해석
전통 DNS + DNSSEC 없음낮음낮음가장 약한 조합. 질의도 보이고 응답도 위조 검출이 어렵다.
전통 DNS + DNSSEC 있음낮음높음응답 진위는 검증하지만 질의 이름은 노출될 수 있다.
DoH/DoT + DNSSEC 없음높음resolver 의존로컬 노출은 줄지만 resolver가 검증하지 않으면 진위 보장은 약하다.
DoH/DoT + DNSSEC 있음높음높음클라이언트-resolver 구간 보호와 응답 검증을 함께 노린다.

여기서 “높음”은 절대적 보장이 아니다. DoH resolver가 어떤 로그를 남기는지, 클라이언트가 실제로 AD 플래그를 확인하는지, DNSSEC validation이 resolver에서 켜져 있는지에 따라 달라진다.

DoH와 DoT는 학습할 때 패킷 캡처보다 응답 차이를 먼저 보면 충분하다. 아래 명령은 “전송 방식이 다를 뿐 같은 A 레코드 질의를 한다”는 감각을 주기 위한 최소 예시다.

Terminal window
# DoH JSON 응답 예시
curl -s "https://cloudflare-dns.com/dns-query?name=example.com&type=A" \
-H "accept: application/dns-json"
# DoT 예시: kdig 사용 환경에서만 실행
kdig @1.1.1.1 +tls-ca example.com A

실습의 관찰 포인트는 세 가지다.

  1. 전통 DNS와 같은 질문을 다른 전송 채널로 보낸다.
  2. DoH 응답은 HTTP 상태 코드와 DNS 응답 코드를 함께 고려해야 한다.
  3. DoT/DoH 성공이 DNSSEC 검증 성공을 자동으로 뜻하지 않는다.

3. DNS 라우팅과 TTL — 이름이 트래픽 정책이 되는 순간

섹션 제목: “3. DNS 라우팅과 TTL — 이름이 트래픽 정책이 되는 순간”

TTL(Time To Live)은 DNS 응답을 캐시에 얼마나 오래 보관할 수 있는지 알려주는 시간이다. 예를 들어 A 레코드 TTL이 300초라면 resolver는 최대 5분 동안 그 응답을 재사용할 수 있다.

TTL은 “전 세계가 이 시간 뒤에 반드시 바뀐다”는 SLA가 아니다. 이미 캐시한 resolver가 TTL 동안 이전 값을 들고 있을 수 있다는 최소한의 전파 지연 모델에 가깝다. 일부 OS, 브라우저, ISP resolver, 중간 캐시는 정책상 더 짧거나 길게 다룰 수도 있다.

TTL 86400초
장점: authoritative DNS 부하가 낮다.
단점: 레코드 변경 후 하루 가까이 이전 값이 남을 수 있다.
TTL 60초
장점: 변경과 failover 반영이 빠르다.
단점: resolver 재조회가 늘고, authoritative 부하와 비용이 커진다.

TTL은 배포 직전에만 바꾸는 값이 아니다. 평소에 긴 TTL을 쓰는 도메인을 마이그레이션하려면, 현재 TTL 이상 전에 미리 낮춰야 한다.

마이그레이션 예시
T-48h: TTL 86400 -> 300으로 낮춤
T-24h: 대부분 resolver의 기존 캐시가 만료됨
T0: A 레코드를 새 IP로 변경
T+1h: 안정 확인 후 TTL을 다시 3600 또는 86400으로 상향

이 예시의 핵심은 T0에 TTL을 낮춰도 늦다는 점이다. 이미 어제 받은 86400초 TTL이 캐시에 남아 있으면, 새 TTL 값 자체를 아직 보지 못한다.

DNS 기반 라우팅은 HTTP reverse proxy나 L4 load balancer와 다르다. DNS는 “다음 요청을 어느 IP/이름으로 보낼지”를 알려줄 뿐, 이미 연결된 TCP 세션을 옮기지 못한다. 클라이언트와 resolver 캐시도 중간에 있다.

DNS routing이 잘하는 일
- 지역별 또는 비율별 endpoint 선택
- 장애 시 새 연결을 대체 endpoint로 유도
- CDN, ALB, API endpoint의 이름 추상화
DNS routing이 못하는 일
- 이미 열린 연결을 즉시 이동
- 사용자별 HTTP 헤더 기반 세밀한 분기
- TTL을 무시하는 클라이언트 캐시 통제

따라서 DNS failover는 보통 “새 조회 또는 TTL 만료 후의 요청”에 효과가 있다. 초 단위 무중단 전환이 필요하면 DNS만으로는 부족하고, load balancer, anycast, service mesh, 애플리케이션 retry 전략까지 함께 봐야 한다.

3-3. Route 53 라우팅 정책 이름을 일반 개념으로 읽기

섹션 제목: “3-3. Route 53 라우팅 정책 이름을 일반 개념으로 읽기”

AWS Route 53은 DNS traffic steering 기능을 여러 policy name으로 제공한다. 제품 이름을 외우기보다 어떤 판단 기준을 DNS 응답에 넣는지 보면 된다.

Route 53 policy name일반 개념언제 쓰는가오해하기 쉬운 지점
Simple단순 응답하나의 대상 또는 여러 값을 그대로 반환장애 대상 자동 제외가 아니다.
Weighted가중치 분배카나리, 점진 배포, A/B 트래픽 비율resolver 캐시 때문에 정확한 사용자 비율과 다를 수 있다.
Latency지연시간 기반 선택여러 리전 중 측정상 빠른 리전으로 안내지리적으로 가까운 곳과 항상 같지 않다.
Geolocation위치 규칙국가/대륙별 언어, 규제, 콘텐츠 분리IP 위치 DB 정확도와 default 레코드가 중요하다.
Failoveractive-passiveprimary health check 실패 시 secondary 안내health check가 실제 사용자 경로를 대표해야 한다.
Geoproximity위치 + bias특정 리전의 영향 범위를 넓히거나 줄임지도상 거리보다 정책 편향을 조절하는 기능이다.
Multivalue answer여러 healthy 값 반환간단한 DNS 레벨 분산과 health check 결합load balancer 대체물이 아니라 제한적 분산이다.

api.example.com을 새 버전으로 10%만 보내고 싶다고 하자. Route 53 Weighted 정책에서는 같은 이름과 타입에 서로 다른 SetIdentifierWeight를 둔다.

api.example.com A
old: 1.2.3.4, weight 90
new: 5.6.7.8, weight 10

계산은 단순하다. 총 weight가 100이면 old는 90%, new는 10%를 목표로 한다. 하지만 이 비율은 “DNS 응답 선택 비율”이지 “실제 사용자 요청 수”가 아니다.

왜 차이가 날까?

차이를 만드는 요소
1. resolver 캐시
한 resolver가 old 응답을 캐시하면 그 resolver 뒤의 많은 사용자가 old로 간다.
2. TTL
TTL이 길면 가중치 변경이 늦게 반영된다.
3. 클라이언트 재시도
앱이 실패 시 같은 IP로 재시도하면 DNS 비율과 요청 비율이 어긋난다.
4. 사용자 분포
대형 ISP resolver 하나가 전체 트래픽의 큰 비중을 차지할 수 있다.

따라서 Weighted는 정밀한 per-request 라우팅이 아니다. 카나리의 첫 단계로는 유용하지만, 정확한 사용자 단위 실험이나 세션 고정이 필요하면 애플리케이션 또는 L7 proxy에서 분기하는 편이 낫다.

Latency 정책은 “사용자 위치가 가장 가까운 리전”을 고르는 기능이 아니다. AWS가 관측한 네트워크 지연 데이터를 기준으로 더 빠른 리전을 고른다. 해저 케이블, ISP 피어링, 클라우드 백본 경로 때문에 물리적으로 가까운 리전이 항상 빠르지 않다.

Geolocation 정책은 반대로 지연시간이 아니라 IP 기반 위치 규칙을 따른다. 한국 IP는 한국 규칙, 유럽 IP는 유럽 규칙처럼 결정한다. 법적 요구, 언어, 콘텐츠 라이선스 같은 정책에는 유용하지만 성능 최적화와 같지 않다.

오해 반례
"한국 사용자는 무조건 ap-northeast-2가 가장 빠르다"
-> ISP 경로에 따라 도쿄, 서울, 싱가포르 중 관측 지연이 달라질 수 있다.
"Geolocation을 쓰면 GDPR을 자동으로 만족한다"
-> 사용자를 EU endpoint로 보내는 것과 데이터 처리·저장 정책 준수는 별도다.

Failover 정책은 primary가 unhealthy일 때 secondary를 반환한다. 핵심은 “무엇을 unhealthy로 볼 것인가”다. 헬스 체크가 사용자 경로를 대표하지 못하면 failover는 작동해도 서비스는 계속 깨질 수 있다.

좋은 health check
- 실제 사용자 요청과 같은 Host 헤더와 TLS 경로를 통과한다.
- 너무 깊은 의존성까지 묶어 불필요한 failover를 만들지 않는다.
- 너무 얕아 실제 장애를 숨기지도 않는다.
나쁜 health check
- nginx welcome page가 200을 반환한다.
- /health가 DB 장애와 무관하게 항상 200이다.
- ALB default target group 응답을 보고 정상으로 오판한다.

여기서 대표성 문제와 도달성 문제를 나눠야 한다. /health가 항상 200이라 실제 장애를 숨기면 silent Healthy가 되고, health checker IP가 방화벽에서 차단되면 false Unhealthy나 불필요한 failover가 된다.

장애 판정은 시간도 포함한다. 30초 간격으로 3회 실패해야 unhealthy라면, 최소 1분~1분 30초의 탐지 지연이 생긴다. 그 뒤 DNS TTL이 남아 있으면 사용자는 더 오래 primary로 갈 수 있다.

failover 지연 감각
health check interval: 30초
failure threshold: 3회
DNS TTL: 60초
장애 발생 후 새 endpoint로 유도되는 시간:
health check 탐지 60~90초
+ resolver TTL 최대 60초
= 대략 2~3분 지연 가능

이 숫자는 DNS failover를 버리라는 뜻이 아니다. DNS failover가 초 단위 즉시 전환 도구가 아니라는 감각을 주기 위한 예시다.


4. CDN 아키텍처 — origin 앞에 재사용 계층을 둔다

섹션 제목: “4. CDN 아키텍처 — origin 앞에 재사용 계층을 둔다”

CDN(Content Delivery Network)은 전 세계 네트워크 거점에 캐시 서버를 두고, 사용자에게 가까운 곳에서 콘텐츠를 제공하는 계층이다. 여기서 “가까움”은 지리적 거리만이 아니라 네트워크 경로와 피어링을 포함한다.

용어왜 중요한가
PoPPoint of Presence. CDN 사업자의 네트워크 거점사용자가 처음 만나는 edge 위치를 결정한다.
edge사용자 가까이 있는 CDN 서버 또는 로직 실행 위치cache hit이면 origin까지 가지 않고 응답한다.
origin원본 서버. S3, ALB, EC2, API 서버, 스토리지 등cache miss와 갱신의 최종 출처다.
cache hitedge가 저장된 응답으로 바로 응답지연과 origin 부하를 줄인다.
cache missedge에 없어서 origin에서 가져옴첫 요청 지연과 origin 부하가 발생한다.
cache key어떤 요청들을 같은 캐시 엔트리로 볼지 정하는 키너무 넓으면 데이터 누출, 너무 좁으면 hit ratio 하락.
invalidation캐시 엔트리를 명시적으로 버리게 하는 조작빠른 반영이 필요할 때 쓰지만 비용과 origin spike가 있다.
사용자
-> 가까운 CDN edge
-> cache hit: edge가 바로 응답
-> cache miss: edge가 origin에 요청
-> origin 응답을 edge에 저장
-> 사용자에게 응답

CloudFront, Cloudflare, Fastly 같은 CDN은 내부 구조가 다르지만, 학습 모델은 비슷하다. 사용자 가까운 edge와 origin 사이에 하나 이상의 중간 캐시 계층이 있을 수 있다. CloudFront의 Regional Edge Cache, Fastly의 shielding, Cloudflare의 tiered cache는 모두 origin으로 가는 miss를 줄이기 위한 중간 계층으로 읽을 수 있다.

단순 모델
browser cache
-> CDN edge cache
-> regional/shield cache
-> origin

이 계층 구조 때문에 “캐시를 지웠다”는 말은 항상 어느 계층인지 물어야 한다. 브라우저 캐시, CDN edge, regional cache, origin 내부 캐시, 애플리케이션 캐시는 서로 다르다.

4-3. Cache Hit Ratio가 origin 부하를 바꾸는 방식

섹션 제목: “4-3. Cache Hit Ratio가 origin 부하를 바꾸는 방식”

Cache Hit Ratio는 전체 요청 중 캐시에서 응답한 비율이다.

Cache Hit Ratio = cache hit 요청 수 / 전체 요청 수

숫자로 보면 CDN의 가치가 명확해진다.

전체 요청: 1,000 req/s
cache hit ratio: 90%
edge에서 응답: 900 req/s
origin까지 도달: 100 req/s

hit ratio가 90%에서 80%로 내려가면 단순히 10%p 나빠진 것이 아니다. origin 입장에서는 100 req/s가 200 req/s로 두 배가 된다.

전체 요청: 1,000 req/s
hit ratio 90% -> origin 100 req/s
hit ratio 80% -> origin 200 req/s
hit ratio 70% -> origin 300 req/s

그래서 x-cache: Miss 비율이 조금만 올라가도 origin CPU, DB, egress 비용이 크게 튈 수 있다. CDN 장애를 볼 때는 평균 TTFB보다 hit ratio와 miss가 origin에 만든 QPS를 함께 본다.

TTFB(Time To First Byte)는 요청을 시작한 뒤 응답의 첫 바이트를 받기까지 걸린 시간이다. CDN은 특히 cache hit에서 TTFB를 줄인다.

origin miss TTFB
사용자 -> edge RTT
+ edge -> origin RTT
+ origin 처리 시간
+ origin -> edge 응답
+ edge -> 사용자 응답
edge hit TTFB
사용자 -> edge RTT
+ edge cache lookup
+ edge -> 사용자 응답

정적 JS/CSS/이미지처럼 origin 계산이 필요 없는 콘텐츠는 edge hit의 효과가 크다. 반대로 사용자별 API처럼 매번 origin 계산과 인증이 필요한 응답은 CDN 앞에 둬도 hit가 나기 어렵다. 이 경우 CDN의 역할은 TLS 종료, 보안 정책, 압축, 라우팅, connection reuse 쪽으로 이동한다.

4-5. 브라우저 resource hint는 CDN을 대체하지 않는다

섹션 제목: “4-5. 브라우저 resource hint는 CDN을 대체하지 않는다”

dns-prefetch는 브라우저가 실제 요청 전에 특정 origin의 DNS 조회를 미리 해두라는 힌트다. preconnect는 DNS 조회뿐 아니라 TCP와 TLS 연결 준비까지 미리 시작하라는 힌트다. 둘 다 CDN 캐시가 아니라 브라우저의 연결 준비 비용을 앞당기는 도구다.

<link rel="dns-prefetch" href="https://analytics.example.com" />
<link rel="preconnect" href="https://cdn.example.com" crossorigin />

사용 기준은 단순하다. 첫 화면에서 곧 사용할 핵심 origin이면 preconnect가 도움이 될 수 있다. 사용할지 불확실한 외부 origin이면 dns-prefetch 정도로 낮춘다. 실제로 쓰지 않을 origin에 preconnect를 남용하면 소켓, TLS 세션, 배터리, 네트워크 슬롯을 낭비한다.

이 절의 목적은 프론트엔드 배포 패턴을 설명하는 것이 아니다. DNS 조회, TCP/TLS 준비, CDN cache hit은 서로 다른 지연 구성요소라는 점을 분리하기 위한 예시다.

cache key는 “두 요청을 같은 캐시 엔트리로 볼 수 있는가”를 결정한다. 대체로 scheme, host, path, 선택된 query string, 선택된 header, 선택된 cookie가 후보가 된다.

요청 A
GET https://cdn.example.com/products?page=1
Accept-Encoding: br
Cookie: session=abc
요청 B
GET https://cdn.example.com/products?page=1
Accept-Encoding: br
Cookie: session=def

만약 Cookie를 cache key에 포함하면 A와 B는 다른 캐시 엔트리다. 사용자별로 캐시가 쪼개져 hit ratio가 낮아진다.

만약 Cookie를 cache key에서 빼고 origin 응답이 사용자별 데이터를 담으면 더 위험하다. A 사용자의 응답이 B 사용자에게 보일 수 있다.

cache key가 너무 좁다
- Authorization, Cookie, 사용자별 query를 무시한다.
- 결과: private data leak 위험.
cache key가 너무 넓다
- User-Agent 전체, 랜덤 query, tracking cookie까지 포함한다.
- 결과: 캐시가 잘게 쪼개져 miss 증가.

CDN 캐시 설계에서 가장 중요한 판단은 “응답이 정말 모든 사용자에게 같은가”다. 공개 정적 자산은 넓게 공유해도 된다. 사용자 프로필, 장바구니, 결제 상태는 공유 캐시에 넣으면 안 된다.

한 이미지 URL이 있다고 하자.

/hero.jpg?utm_source=ad1
/hero.jpg?utm_source=ad2
/hero.jpg?utm_source=newsletter

이미지 바이트가 완전히 같다면 utm_source는 캐시 키에 들어가면 안 된다. 쿼리스트링을 모두 cache key에 포함하면 같은 이미지가 세 엔트리로 쪼개진다.

쿼리 전체 포함
/hero.jpg?utm_source=ad1 -> cache entry 1
/hero.jpg?utm_source=ad2 -> cache entry 2
/hero.jpg?utm_source=newsletter -> cache entry 3
utm_source 제외
/hero.jpg -> cache entry 1

반대로 이미지 리사이징 API가 w=400w=800으로 다른 바이트를 만든다면 w는 cache key에 들어가야 한다. 이 값을 빼면 400px 이미지가 800px 요청에 재사용될 수 있다.

포함해야 하는 query
- w, h, format처럼 응답 바이트를 바꾸는 값
제외하기 쉬운 query
- utm_source, fbclid, gclid처럼 추적용이고 응답 바이트를 바꾸지 않는 값

HTTP 응답의 Cache-Control: max-age=3600은 캐시가 1시간 동안 응답을 fresh로 볼 수 있다는 뜻이다. CDN은 이 시간 동안 origin에 다시 묻지 않고 응답할 수 있다.

Cache-Control: public, max-age=3600

장점은 단순하다. 배포 시스템이 별도 무효화 API를 호출하지 않아도 된다. 단점은 바뀐 내용을 즉시 반영하기 어렵다는 점이다.

긴 TTL
hit ratio 높음
origin 부하 낮음
변경 반영 느림
짧은 TTL
변경 반영 빠름
origin 부하 높음
edge cache 가치 낮아짐

invalidation은 CDN에게 특정 경로의 캐시를 버리라고 명령하는 것이다. CloudFront에서는 invalidation, Cloudflare/Fastly에서는 purge라는 용어를 자주 쓴다.

invalidation이 필요한 상황
- 잘못된 HTML이 배포되어 즉시 내려야 한다.
- 법적 문제나 보안 문제로 공개 자산을 더 제공하면 안 된다.
- versioned URL을 적용하지 못한 legacy 파일이 바뀌었다.

invalidation은 만능이 아니다. CDN edge 캐시를 지워도 브라우저 캐시가 이미 오래 들고 있으면 사용자는 계속 구버전을 볼 수 있다. 또한 많은 경로를 한꺼번에 지우면 다음 요청이 모두 origin으로 몰리는 spike가 생길 수 있다.

전체 무효화 "/*"의 숨은 비용
1. edge cache가 비워진다.
2. 사용자 요청이 cache miss로 origin에 몰린다.
3. origin CPU, DB, egress가 순간 증가한다.
4. 인기 자산이 다시 warm-up될 때까지 TTFB가 나빠진다.

따라서 정기 배포의 기본 전략은 invalidation이 아니라 versioned URL이다.

versioned URL은 파일 내용이 바뀌면 URL도 바꾸는 전략이다. 가장 흔한 형태는 파일명에 content hash를 넣는 것이다.

변경 전: /assets/app.abc123.js
변경 후: /assets/app.def456.js

이 방식에서는 JS/CSS/이미지 같은 정적 자산에 긴 TTL을 줄 수 있다. 내용이 바뀌면 URL이 달라져 새 cache entry가 만들어지기 때문이다.

정적 자산
Cache-Control: public, max-age=31536000, immutable
HTML entrypoint
Cache-Control: no-cache 또는 max-age=0, must-revalidate

HTML은 보통 짧게 둔다. HTML이 어떤 해시 파일을 참조할지 결정하기 때문이다. 오래된 HTML이 새 배포 후에도 남아 있으면 더 이상 존재하지 않는 JS 파일을 참조하거나, 구버전 애플리케이션을 계속 띄울 수 있다.

이 예시는 SPA에만 해당하지 않는다. SSR 페이지도 s-maxagestale-while-revalidate를 조합할 수 있고, 정적 chunk는 content hash로 장기 캐시할 수 있다. 핵심은 “entrypoint는 짧게, content-addressed asset은 길게”라는 캐시 수명 분리다.

stale-while-revalidate는 캐시가 만료된 응답을 잠깐 계속 제공하면서 백그라운드에서 새 응답을 가져오게 하는 지시어다. 사용자에게 빠른 응답을 주고 freshness 갱신을 뒤로 미루는 패턴이다.

Cache-Control: public, max-age=60, stale-while-revalidate=300

해석은 다음과 같다.

0~60초
fresh 응답. 캐시가 즉시 제공한다.
60~360초
stale이지만 제공 가능.
캐시는 사용자에게 기존 응답을 주고, 뒤에서 origin 갱신을 시도한다.
360초 이후
더 이상 stale로 제공하지 않고 origin 재검증이 필요하다.

이 전략은 뉴스 목록, 상품 목록, 공개 설정처럼 수십 초~수분의 지연을 받아들일 수 있는 콘텐츠에 잘 맞는다. 실시간 재고, 결제 상태, 사용자 권한 같은 데이터에는 위험하다.

stale-if-error는 origin이 500, 502, 503, 504 같은 에러를 반환하거나 연결 실패가 났을 때, 만료된 캐시를 일정 시간 더 제공할 수 있게 한다. 가용성을 높이는 대신 freshness를 희생한다.

Cache-Control: public, max-age=300, stale-if-error=86400

해석은 다음과 같다.

0~300초
fresh 캐시 제공.
300초 이후 origin 정상
origin에서 새 응답을 가져와 갱신.
300초 이후 origin 장애
최대 86400초 동안 구버전 응답 제공 가능.

이 패턴은 장애 전파를 줄인다. origin이 잠시 죽어도 사용자는 에러 대신 어제의 블로그 글이나 공개 문서를 볼 수 있다. 하지만 결제 가격, 재고, 로그인 상태, 권한 응답에는 쓰면 안 된다.

콘텐츠stale-if-error 적합성이유
블로그 글, 문서, 공개 마케팅 페이지높음구버전이 에러보다 낫다.
상품 목록중간가격·재고 정확도 요구에 따라 달라진다.
사용자 프로필낮음사용자별 정보와 권한 노출 위험이 있다.
결제/주문 API매우 낮음구버전 응답이 금전 사고로 이어질 수 있다.
응답 유형권장 전략주의할 점
content hash가 있는 JS/CSS/image긴 TTL + immutable파일명에 실제 content hash가 있어야 한다.
HTML entrypoint짧은 TTL 또는 revalidate새 asset URL을 빨리 받게 해야 한다.
공개 목록 API짧은 s-maxage + stale-while-revalidatequery가 응답을 바꾸는지 cache key를 점검한다.
사용자별 APIprivate 또는 no-storeAuthorization/Cookie 응답을 공유 캐시에 넣지 않는다.
장애 시 보여도 되는 공개 콘텐츠stale-if-errorfreshness보다 가용성이 우선인지 확인한다.
법적 삭제 대상 콘텐츠짧은 TTL + purge 체계브라우저 캐시와 downstream 복제까지 고려한다.

6. CDN 설정을 공급자 예시로 읽기

섹션 제목: “6. CDN 설정을 공급자 예시로 읽기”

6-1. CloudFront 구성 요소를 일반 모델로 번역하기

섹션 제목: “6-1. CloudFront 구성 요소를 일반 모델로 번역하기”

CloudFront를 예로 들면 Distribution, Origin, Behavior, Cache Policy, Origin Request Policy, Response Headers Policy가 자주 나온다. 이 이름들은 AWS 고유 명칭이지만, 일반 CDN 모델로 번역하면 어렵지 않다.

Distribution
이 CDN 배포 전체의 외부 엔드포인트와 설정 묶음.
Origin
miss가 났을 때 가져올 원본.
S3, ALB, API 서버, 다른 HTTP 서버가 될 수 있다.
Behavior
path pattern별 처리 규칙.
/static/*, /api/*, /* 같은 경로에 다른 origin/cache policy를 붙인다.
Cache Policy
cache key와 TTL을 결정한다.
Origin Request Policy
cache key에는 넣지 않더라도 origin에 전달할 header/cookie/query를 정한다.
Response Headers Policy
HSTS, CSP, CORS 같은 응답 헤더를 edge에서 추가하거나 조정한다.

중요한 구분은 Cache Policy와 Origin Request Policy다. 둘 다 header, cookie, query를 다루지만 목적이 다르다.

Cache Policy
"캐시 엔트리를 나누는 기준"을 정한다.
Origin Request Policy
"origin이 요청을 처리하는 데 필요한 정보"를 정한다.

예를 들어 CloudFront-Viewer-Country는 origin이 지역별 로깅이나 표시 문구에 참고할 수 있다. 하지만 이 값이 응답 바이트를 바꾸지 않는다면 cache key에는 넣지 않는 편이 좋다. 반대로 응답 언어가 국가별로 달라진다면 cache key에 포함하거나 경로를 분리해야 한다.

하나의 도메인에 정적 자산, HTML, API가 섞여 있다고 하자.

/assets/*
origin: object storage
cache: 1년, immutable
key: path + Accept-Encoding
/index.html
origin: object storage
cache: no-cache 또는 짧은 TTL
key: path + Accept-Encoding
/api/public/*
origin: app server
cache: 30~300초
key: path + 필요한 query
/api/me/*
origin: app server
cache: disabled 또는 private
key: 공유 캐시에 넣지 않음

이 예시는 SPA/SSR 배포 절차가 아니라 cacheability 분류 예시다. 프레임워크가 React, Next.js, Astro, Rails, Spring 무엇이든 원칙은 같다. 응답이 모든 사용자에게 같은지, 얼마나 stale해도 되는지, 어떤 요청 요소가 응답 바이트를 바꾸는지를 먼저 묻는다.

Authorization 헤더가 있는 요청은 기본적으로 공유 캐시에 넣지 않는다고 생각하는 편이 안전하다. 공개 API 중에서도 bearer token을 단순 rate limit 식별자로만 쓰는 특수한 경우가 있지만, 첫 설계에서는 private로 보는 것이 맞다.

위험한 설계
GET /api/me
Authorization: Bearer userA
cache key = path only
결과:
userA의 /api/me 응답이 캐시에 저장됨.
userB가 /api/me 요청.
cache key가 같아서 userA 응답을 받을 수 있음.

반대로 모든 header를 cache key에 넣으면 데이터 누출 위험은 줄지만 hit ratio가 무너진다. User-Agent, Accept-Language, Cookie, 추적 query가 무분별하게 들어가면 같은 콘텐츠도 수천 개 엔트리로 쪼개진다.

좋은 설계는 중간이다. 응답 바이트를 바꾸는 값만 key에 넣고, 나머지는 origin 전달 여부를 별도로 판단한다.

CDN 선택은 “가장 빠른 CDN” 하나로 결정되지 않는다. 팀이 이미 쓰는 인프라, 캐시 제어의 복잡도, 보안 기능, 로그/분석, 비용 모델, 엣지 코드 요구가 함께 작동한다.

기준CloudFront 예시Cloudflare 예시Fastly 예시
기존 인프라AWS origin, IAM, S3/ALB 연동이 강하다DNS·WAF·DDoS 통합이 쉽다VCL과 실시간 로그 제어가 강하다
캐시 제어policy 기반으로 표준화하기 좋다purge와 보안 기능을 한 플랫폼에서 다루기 좋다surrogate key, shielding, 세밀한 edge 제어에 강하다
엣지 코드Functions, Lambda@EdgeWorkersCompute@Edge
운영 관찰CloudWatch와 AWS 로그 흐름대시보드와 edge 보안 이벤트실시간 로그 스트리밍과 세밀한 분석

학습 관점의 결정 질문은 다음과 같다.

1. origin이 어디에 있는가?
2. cache key를 얼마나 세밀하게 제어해야 하는가?
3. purge가 얼마나 자주, 얼마나 빠르게 필요할 것인가?
4. WAF/Bot/DDoS 방어를 같은 플랫폼에서 다룰 것인가?
5. edge에서 실행할 로직이 단순 rewrite인지, 외부 fetch와 무거운 계산인지?
6. 로그가 몇 분 뒤 집계되어도 되는가, 거의 실시간으로 필요한가?

이 질문에 답하면 공급자 비교표가 암기가 아니라 설계 판단으로 바뀐다. 선택한 뒤에는 p95 TTFB, cache hit ratio, purge/invalidation 전파 시간, origin egress, 로그 지연 시간을 기준선으로 재야 한다. “벤더를 바꿨다”가 아니라 “edge hit이 늘고, miss 때 origin 보호가 되는가”가 검증 질문이다.

DNS 서비스도 마찬가지다. Route 53, Cloudflare DNS, NS1 같은 서비스는 권한 DNS를 제공하지만 강점이 다르다.

AWS 스택 중심
- ALB, CloudFront, S3 website endpoint, health check와 연결이 쉽다.
- Route 53 policy name으로 traffic steering을 구성하기 좋다.
보안/프록시 통합 중심
- Cloudflare DNS와 CDN/WAF/Bot 보호를 한 곳에서 다룰 수 있다.
- proxied record를 쓰면 DNS와 HTTP 프록시 경계가 함께 움직인다.
고급 traffic steering 중심
- 세밀한 steering, 실시간 상태, multi-CDN을 기준으로 별도 DNS 플랫폼을 검토한다.

선택 기준은 기능 목록보다 장애 경계에서 드러난다. 누가 authoritative DNS를 운영하는가, 장애 시 어떤 API로 바꿀 수 있는가, DNSSEC DS 등록 흐름은 어디에서 관리하는가, 로그와 audit trail은 충분한가를 확인해야 한다.


7. 엣지 컴퓨팅 — 캐시 근처에서 어디까지 실행할 것인가

섹션 제목: “7. 엣지 컴퓨팅 — 캐시 근처에서 어디까지 실행할 것인가”

엣지 컴퓨팅은 origin보다 사용자 가까운 CDN edge 또는 regional edge에서 코드를 실행하는 방식이다. 목적은 모든 로직을 edge로 옮기는 것이 아니라, 가까이 실행할 가치가 있는 작은 판단을 origin 전에 처리하는 것이다.

edge에 잘 맞는 작업
- URL rewrite/redirect
- header 추가/정규화
- country/device 기반 가벼운 분기
- A/B 테스트 쿠키 부여
- 정적 파일 fallback
origin에 남겨야 하는 작업
- DB transaction
- 긴 외부 API 호출
- 무거운 이미지/영상 처리
- 복잡한 권한 정책과 감사 로깅
- 실패 시 일관성 보장이 필요한 비즈니스 로직

edge 로직은 지연시간을 줄일 수 있지만 관찰성과 배포 복잡도를 높인다. 로그가 지역별로 흩어지고, 런타임 제약이 있으며, 장애가 전 세계 edge에 빠르게 퍼질 수 있다. 실제 공급자마다 제한은 다르지만 선택 기준은 비슷하다. 실행 시간 제한, 메모리 한도, 외부 네트워크 접근 가능 여부, 사용할 수 있는 런타임, 배포가 어느 edge 범위까지 퍼지는지가 edge compute의 경계다.

7-2. CloudFront Functions와 Lambda@Edge를 예시로 읽기

섹션 제목: “7-2. CloudFront Functions와 Lambda@Edge를 예시로 읽기”

CloudFront Functions는 CloudFront viewer request/response 단계에서 실행되는 가벼운 JavaScript 함수다. 주로 URL rewrite, redirect, header 조작처럼 매우 짧고 deterministic한 작업에 맞는다.

Lambda@Edge는 CloudFront 이벤트에 연결되는 더 무거운 함수 실행 모델이다. viewer event뿐 아니라 origin request/response 단계에서도 쓸 수 있고, 런타임과 실행 시간이 더 넓다. 대신 배포, 콜드 스타트, 비용, 권한 관리가 더 무겁다.

판단 기준CloudFront FunctionsLambda@Edge
실행 위치모든 edge의 viewer 단계 중심viewer와 origin 단계
작업 성격짧은 rewrite, redirect, header 조작인증, origin 선택, 응답 변환 등 더 복잡한 작업
네트워크/라이브러리제약이 강하다상대적으로 넓지만 운영 부담이 크다
실패 영향매우 앞단에서 모든 요청에 영향더 복잡한 로직 실패 가능성

공급자가 달라도 선택 질문은 같다.

이 로직은 cache key를 바꾸는가?
이 로직은 사용자별 private data를 읽는가?
이 로직은 실패하면 origin으로 안전하게 fallback할 수 있는가?
이 로직을 edge에 두면 관찰과 배포가 감당 가능한가?

클라이언트 라우팅을 쓰는 정적 앱에서 /users/123을 새로고침하면 origin object storage에는 해당 파일이 없을 수 있다. 이때 edge에서 확장자가 없는 경로를 /index.html로 바꾸면 object storage의 404/403을 피할 수 있다.

function handler(event) {
var request = event.request;
var uri = request.uri;
if (!uri.includes(".")) {
request.uri = "/index.html";
}
return request;
}

이 예시는 “프론트엔드 배포 절차”가 아니라 edge rewrite의 경계를 보여준다. 요청 경로만 바꾸면 되는 문제라면 가벼운 edge function이 적합하다. DB 조회나 사용자 권한 판정이 필요하다면 origin이나 별도 인증 계층으로 넘기는 편이 낫다. 실제 배포에서는 behavior나 route scope를 먼저 좁혀야 한다. /api/*, /auth/*, webhook처럼 확장자가 없지만 HTML fallback 대상이 아닌 경로까지 /index.html로 바꾸면 API 장애를 정적 앱 라우팅 문제처럼 숨길 수 있다.

결제 API 앞에서 edge function이 가격을 계산한다고 해보자. 처음에는 origin 부하를 줄이는 것처럼 보일 수 있다. 하지만 가격은 재고, 쿠폰, 사용자 등급, 세금, 감사 로그와 연결된다. edge 런타임에서 이 모든 의존성을 안정적으로 다루기 어렵고, 실패 시 정합성 사고가 난다.

edge 부적합 신호
1. DB transaction이 필요하다.
2. 사용자별 권한과 감사 로그가 핵심이다.
3. 외부 API 실패에 따라 보상 트랜잭션이 필요하다.
4. stale 응답이 금전·권한 사고가 된다.
5. 장애 시 "그냥 origin으로 보내면 된다"가 성립하지 않는다.

edge는 가까운 실행 위치이지, 분산 트랜잭션 플랫폼이 아니다.


증상은 단순하다. 일부 사용자는 새 IP로 가고, 일부 사용자는 구 IP로 간다. dig @8.8.8.8, dig @1.1.1.1, ISP resolver 결과가 서로 다를 수 있다.

핵심 원리는 TTL과 캐시다. 변경이 authoritative DNS에 반영돼도, resolver가 이전 응답을 TTL 동안 들고 있으면 사용자는 구 값으로 간다.

관찰 신호
- resolver별 A/CNAME 결과가 다르다.
- authoritative server를 직접 조회하면 새 값이다.
- recursive resolver에는 구 값이 남아 있다.
먼저 의심할 것
- 변경 전 TTL을 충분히 낮췄는가?
- CNAME 체인 중간의 TTL이 더 긴가?
- OS/browser/application DNS cache가 따로 있는가?

해결 절차보다 중요한 교훈은 마이그레이션 전 TTL 낮추기다. DNS 변경은 “지금 버튼을 누르면 전 세계가 바뀐다”가 아니라 “캐시된 약속이 만료되기를 기다린다”에 가깝다.

NXDOMAIN은 존재하지 않는 이름에 대한 응답이다. 봇이 무작위 서브도메인을 계속 질의하면 캐시가 잘 먹지 않는다.

random001.example.com
random002.example.com
random003.example.com
...

각 이름이 다르므로 recursive resolver는 authoritative server에 계속 묻는다. 권한 DNS가 과부하될 수 있다.

대응은 서비스 정책에 따라 다르다. wildcard 레코드로 모든 이름을 같은 응답으로 보낼 수 있지만, 잘못 쓰면 오타 도메인까지 정상 응답처럼 보인다. rate limiting, DNS firewall, authoritative provider의 DDoS 방어를 함께 검토한다.

학습 포인트는 “존재하지 않는 이름도 캐시와 서명 정책의 일부”라는 점이다. DNSSEC를 쓴다면 NSEC/NSEC3 부정적 응답도 함께 이해해야 한다.

silent failure는 시스템이 정상이라고 말하지만 사용자는 실패하는 상황이다. DNS failover에서 특히 위험하다.

사용자: /api/orders에서 503
Route 53 health check: Healthy
결과: failover가 발동하지 않음

흔한 원인은 health check path가 실제 사용자 경로를 대표하지 않는 것이다. /health가 항상 200을 반환하거나, Host 헤더가 달라 default target group을 보고 있거나, DB 장애를 shallow check가 숨길 수 있다.

진단 질문
1. health checker가 실제로 어떤 Host와 path를 요청하는가?
2. TLS 인증서와 SNI 경로가 사용자 요청과 같은가?
3. shallow check와 deep check의 역할이 명확한가?
4. ALB 5xx가 증가하는데 health check는 Healthy인가?

shallow check는 “프로세스와 라우팅이 살아 있는가”를 보는 데 좋다. deep check는 DB, Redis, 외부 API까지 보는 데 좋다. Route 53 failover에 어느 쪽을 연결할지는 장애 전파 비용과 오탐 비용을 비교해 결정한다.

반대로 health checker source IP가 방화벽에서 막히면 사용자는 정상인데 health check만 실패할 수 있다. 이 경우는 silent Healthy가 아니라 false Unhealthy 문제로 분리해 본다.

DNSSEC 장애는 사용자가 “도메인이 갑자기 안 열린다”로 인식한다. 하지만 NXDOMAIN과 다르다. 이름이 없는 것이 아니라, 검증할 수 없어서 실패하는 것이다.

관찰 신호
- 검증 resolver에서 SERVFAIL
- 비검증 또는 +cd 조회에서는 레코드가 보임
- DS와 DNSKEY key tag가 맞지 않음
- RRSIG 만료 또는 알고리즘 불일치

가장 흔한 학습 사례는 KSK/DS 불일치다. 부모 존에는 구 KSK의 DS가 남아 있는데, 자식 존은 새 KSK만 공개하면 chain of trust가 끊긴다.

부모 .com
DS: old KSK hash
자식 example.com
DNSKEY: new KSK only
resolver
old DS로 new KSK를 검증하려 함
해시 불일치
SERVFAIL

이 실패는 DNSSEC가 약해서가 아니라, 신뢰 체인을 엄격하게 검증하기 때문에 보이는 실패다. 운영자는 구 키와 새 키가 캐시 TTL 동안 겹치는 기간을 설계해야 한다.

8-5. 구버전 콘텐츠가 계속 보인다

섹션 제목: “8-5. 구버전 콘텐츠가 계속 보인다”

배포 후에도 사용자가 구버전 JS나 HTML을 본다면, 먼저 어느 캐시 계층인지 나눠야 한다.

브라우저 캐시
DevTools disable cache에서 사라지는가?
CDN edge
x-cache: Hit
Age가 증가하는가?
regional cache 또는 shield
edge purge 후에도 일부 지역에서만 구버전인가?
origin
origin 직접 조회도 구버전인가?

구버전 JS 파일이 계속 보이는 근본 원인은 대개 content hash 없는 긴 TTL이다. /app.js에 1년 TTL을 주면, 배포 후에도 같은 URL이므로 캐시가 새 파일인지 알 수 없다.

나쁜 패턴
/app.js
Cache-Control: max-age=31536000
좋은 패턴
/app.abc123.js
Cache-Control: max-age=31536000, immutable
/index.html
Cache-Control: no-cache

invalidation은 사고 대응에는 유용하지만 정기 배포의 핵심 전략이 되면 취약하다. 배포 때마다 /*를 지우면 origin spike와 비용을 반복해서 만든다.

CDN을 붙였는데 TTFB가 크게 나아지지 않는다면 hit ratio를 본다. miss가 높으면 사용자 요청이 계속 origin까지 간다.

원인 후보
1. Cache-Control이 no-store 또는 private이다.
2. cache key에 불필요한 query/header/cookie가 들어간다.
3. 응답마다 Set-Cookie가 붙어 공유 캐시가 꺼진다.
4. Authorization 요청을 캐시하려고 해서 CDN이 우회한다.
5. TTL이 너무 짧아 인기 객체도 금방 만료된다.
6. purge가 너무 자주 발생한다.

좋은 진단은 하나의 인기 URL을 고정해 반복 요청하는 것이다. 첫 요청은 miss, 두 번째 요청은 hit가 되어야 한다. 반복해도 miss라면 cacheability 또는 key가 잘못됐을 가능성이 크다.

Terminal window
curl -sI https://cdn.example.com/assets/app.abc123.js
curl -sI https://cdn.example.com/assets/app.abc123.js

관찰 포인트는 Cache-Control, Age, Via, x-cache 또는 공급자별 cache status header다. 헤더 이름은 공급자마다 다르지만 질문은 같다. “이 응답이 저장 가능한가”, “저장됐다면 같은 key로 다시 찾는가”.

origin이 죽었을 때 CDN이 할 수 있는 일은 콘텐츠 성격에 따라 다르다.

정적 문서
stale-if-error로 구버전 제공 가능.
공개 목록
짧은 시간 stale 허용 가능.
사용자별 API
stale 제공 위험.
결제/주문
stale 제공 금지에 가깝다.

stale serving이 좋은 장애 대응이 되려면 사전에 header와 policy가 있어야 한다. 장애가 난 뒤에 “어제 버전을 보여주자”고 결정해도, edge에 캐시된 사본이 없거나 policy가 금지하면 어렵다.

stale 전략의 전제
1. 평소에 cacheable한 응답이어야 한다.
2. stale 허용 시간이 명시되어 있어야 한다.
3. stale해도 안전한 콘텐츠여야 한다.
4. 사용자가 stale임을 알아야 하는 경우 표시 전략이 있어야 한다.

stale은 장애를 숨기는 도구가 아니다. 사용자에게 에러 대신 제한된 구버전을 제공하는 trade-off다.

split-horizon DNS는 내부 네트워크와 외부 네트워크에서 같은 이름이 다른 응답을 갖는 구조다. 예를 들어 VPC 내부의 api.example.com은 private ALB를 가리키고, 외부에서는 public ALB를 가리킬 수 있다.

Public DNS
api.example.com -> public ALB
Private DNS
api.example.com -> internal ALB

이 구조는 유용하지만 디버깅을 어렵게 만든다. 노트북에서 정상인 이름이 Lambda나 EC2 내부에서는 다른 IP로 풀릴 수 있다. 반대로 내부에서만 정상인 이름이 외부 사용자에게는 NXDOMAIN일 수 있다.

실패 신호는 “실행 위치에 따라 DNS 결과가 다르다”다. 같은 dig라도 어느 네트워크에서 실행했는지, 어떤 resolver를 썼는지, private hosted zone이 연결된 VPC인지 확인해야 한다.


DNSSEC는 도메인 신뢰를 강화하지만 운영 절차도 추가한다. 특히 registrar, DNS provider, parent zone DS 등록, 키 롤오버 자동화가 모두 맞아야 한다.

DNSSEC를 우선 검토할 상황
- 피싱과 DNS 위조 리스크가 큰 공개 도메인
- 메일, 인증, 금융, 공공 성격의 도메인
- 조직 차원에서 DNS 변경 감사와 키 관리 체계가 있는 경우
주의할 상황
- DNS provider와 registrar 책임 경계가 불명확한 경우
- 키 롤오버 절차를 아무도 모르는 경우
- 서브도메인 위임이 복잡하고 DS 관리가 흩어진 경우

DNSSEC를 켜지 말자는 뜻이 아니다. 서명 체인이 깨지면 더 안전하게 실패하기 때문에, 운영팀은 그 실패를 읽을 수 있어야 한다.

목표먼저 볼 정책피해야 할 오해
단일 endpoint 연결Simple여러 IP 반환이 health-aware load balancing은 아니다.
새 버전 5~10% 노출Weighted실제 사용자 비율은 resolver 캐시 때문에 흔들린다.
빠른 리전 선택Latency법적 위치 분리와 다르다.
국가별 콘텐츠GeolocationIP 위치 정확도와 default 레코드가 필요하다.
active-passive 장애 대응Failoverhealth check와 TTL 지연을 계산해야 한다.
리전 영향권 조정Geoproximitybias는 성능 측정값 자체가 아니다.

아래 질문에 모두 “예”에 가까울수록 공유 CDN 캐시가 적합하다.

1. 모든 사용자에게 같은 응답인가?
2. 몇 초~몇 시간 stale해도 안전한가?
3. 응답을 바꾸는 query/header/cookie가 명확한가?
4. content hash나 invalidation 전략이 있는가?
5. origin 부하 또는 사용자 RTT를 줄이는 이득이 큰가?

하나라도 “아니오”라면 캐시를 금지하거나 private로 두는 편이 안전하다. 특히 사용자별 인증 응답은 성능 욕심보다 데이터 경계를 우선해야 한다.

stale은 “빠른데 오래된 응답”이다. 가용성과 freshness 사이의 명시적 선택이다.

stale을 허용하기 좋은 조건
- 공개 콘텐츠다.
- 오래된 응답이 에러보다 낫다.
- 사용자에게 약간의 지연 업데이트가 허용된다.
- origin 장애 때도 cached copy가 남아 있다.
stale을 피해야 하는 조건
- 금액, 권한, 재고, 결제 상태가 바뀐다.
- 사용자별 응답이다.
- stale 응답이 법적 문제를 만든다.
- 장애를 숨기면 운영 탐지가 늦어진다.

edge compute는 “가깝게 실행하면 좋은가”와 “분산해서 운영할 수 있는가”를 함께 물어야 한다.

edge 적합
- 입력이 request header/path/query 정도로 충분하다.
- 실행 시간이 매우 짧다.
- 실패 시 원래 request로 fallback할 수 있다.
- cache key 정규화나 redirect처럼 edge 위치의 이득이 분명하다.
edge 부적합
- origin DB와 transaction이 필요하다.
- 외부 API 실패가 business state를 바꾼다.
- 로그와 감사 추적이 중앙 집중으로 필요하다.
- 배포 실수가 전 세계 요청을 동시에 망가뜨릴 수 있다.

10. 전이 분석 — 다른 캐시·신뢰 체인에도 적용하기

섹션 제목: “10. 전이 분석 — 다른 캐시·신뢰 체인에도 적용하기”

DNS와 CDN에서 배운 구조는 다른 시스템에도 반복된다. 새 기술을 만났을 때 아래 질문을 던지면 이름보다 메커니즘을 먼저 볼 수 있다.

원리DNS/CDN다른 시스템 예시읽는 질문
Chain of trustDNSSEC root -> TLD -> zoneTLS CA chain, JWT issuer key신뢰의 root는 누구인가? 위임은 어디서 끊기는가?
다단 캐시browser -> edge -> regional -> originCPU cache, DB buffer cache, HTTP cache어느 계층에 stale 데이터가 남는가?
TTLDNS TTL, HTTP max-agetoken expiry, cache expiry만료 전 변경은 어떻게 전파되는가?
InvalidationCDN purge, versioned URLDB materialized view refresh, token revocationpush로 지우는가, pull로 만료를 기다리는가?
stale servingstale-if-errorcircuit breaker fallback, read replica lag tolerance오래된 응답이 에러보다 나은가?

10-1. 새 캐시 시스템을 만났을 때 질문

섹션 제목: “10-1. 새 캐시 시스템을 만났을 때 질문”
  1. cache key는 무엇인가?
  2. key가 너무 넓거나 좁으면 어떤 사고가 나는가?
  3. TTL은 누가 정하고 어느 계층에서 적용되는가?
  4. invalidation은 즉시 전파되는가, 큐에 쌓이는가?
  5. stale을 허용하는가, 허용한다면 얼마나 오래인가?
  6. cache miss가 origin 또는 backing store에 만드는 부하는 얼마인가?
  7. 장애 시 캐시가 에러를 숨기는가, 아니면 빠르게 드러내는가?

10-2. 새 신뢰 체인을 만났을 때 질문

섹션 제목: “10-2. 새 신뢰 체인을 만났을 때 질문”
  1. trust anchor는 어디에 저장되는가?
  2. 중간 위임자는 누구이고 어떤 키로 서명하는가?
  3. 폐기 또는 교체는 어떻게 전파되는가?
  4. 캐시에 이전 키가 남아 있을 때 새 서명을 검증할 수 있는가?
  5. 실패는 open인가, closed인가?

DNSSEC는 실패 closed에 가깝다. 검증할 수 없으면 응답을 쓰지 않는다. 이 성질은 보안을 높이지만, 운영자가 chain break를 만들면 정상 도메인도 불통이 된다.


주제핵심 문장
DNSSECDNS 응답에 서명을 붙여 RRset의 진위와 무결성을 검증한다. 암호화는 아니다.
RRSIG/DNSKEY/DSRRSIG는 레코드 서명, DNSKEY는 검증 공개키, DS는 부모와 자식 존을 잇는 해시다.
ZSK/KSKZSK는 레코드 서명, KSK는 키 묶음과 DS 연결을 담당한다.
NSEC/NSEC3존재하지 않는 이름도 검증 가능한 “없음” 응답으로 만든다.
DoH/DoT클라이언트와 resolver 사이 DNS 메시지를 TLS/HTTPS로 보호한다. resolver 신뢰 문제는 남는다.
TTLDNS 응답 캐시 시간이다. 전파 지연과 authoritative 부하 사이의 trade-off다.
Route 53 정책Simple, Weighted, Latency, Geolocation, Failover, Geoproximity는 DNS 응답 선택 기준의 이름이다.
PoP/edge/originPoP와 edge는 사용자 가까운 CDN 거점, origin은 원본 서버다.
Cache key같은 응답으로 재사용할 요청을 구분하는 기준이다. 너무 넓어도 좁아도 사고가 난다.
Invalidation캐시를 명시적으로 버리는 조작이다. 정기 배포의 기본값은 versioned URL이 더 안전하다.
stale-if-errororigin 장애 시 만료된 응답을 제한적으로 제공한다. 공개 콘텐츠에는 유용하지만 결제·권한에는 위험하다.
Edge compute가까운 위치에서 짧은 로직을 실행한다. 트랜잭션과 무거운 비즈니스 로직을 옮기는 도구가 아니다.

이 절은 운영 런북이 아니라 관찰 실습이다. 실제 도메인과 CDN이 있다면 같은 질문을 자신의 환경에 적용한다.

Terminal window
dig +dnssec cloudflare.com A
dig +dnssec +cd cloudflare.com A

볼 것:

1. flags에 ad가 있는가?
2. ANSWER에 RRSIG가 같이 오는가?
3. 검증 resolver와 비검증 옵션(`+cd`, checking disabled)의 결과가 어떻게 다른가?
Terminal window
dig +noall +answer example.com A @a.iana-servers.net
for resolver in 8.8.8.8 1.1.1.1 9.9.9.9; do
echo "$resolver"
dig +noall +answer example.com A @"$resolver"
done

볼 것:

1. resolver별 답이 같은가?
2. 남은 TTL이 얼마인가?
3. authoritative server 직접 조회와 recursive 조회가 다른가?
Terminal window
curl -sI https://cdn.example.com/assets/app.abc123.js
curl -sI https://cdn.example.com/assets/app.abc123.js

볼 것:

1. 첫 번째와 두 번째 요청의 cache status가 달라지는가?
2. Age가 증가하는가?
3. Cache-Control이 CDN 저장을 허용하는가?
4. 같은 URL인데 query/header 때문에 key가 달라지지는 않는가?

아래 응답에 stale-if-error=86400을 붙여도 되는지 판단해보자.

응답판단
/docs/getting-started 공개 문서대체로 가능. 구버전이 에러보다 낫다.
/products?category=book 공개 목록조건부 가능. 가격·재고 정확도 요구를 확인한다.
/me 사용자 정보부적합. 사용자별 응답이다.
/orders/123/pay 결제 요청부적합. stale 응답이 금전 사고로 이어질 수 있다.

다음 요청 요소를 cache key에 넣을지 판단한다.

요소보통의 판단이유
path포함다른 경로는 다른 리소스다.
Accept-Encoding포함 또는 CDN 내부 처리gzip/br 응답 바이트가 달라질 수 있다.
utm_source제외대개 응답 바이트를 바꾸지 않는다.
w 이미지 폭포함이미지 바이트를 바꾼다.
Authorization공유 캐시 금지에 가깝다사용자별 응답 위험이 크다.
전체 Cookie피한다hit ratio를 크게 낮추고 private data 위험이 있다.