DNS

worldclasscitizen·2026년 4월 23일

DNS는 전화번호부가 아닙니다

"그냥 도메인을 IP로 바꿔주는 거 아니야?" — 이 한 문장으로 DNS를 설명하는 글을 여러 번 봤습니다. 이 글은 그 문장이 왜 DNS의 10%도 설명하지 못하는지에 대한 이야기입니다.


📌 TL;DR

  • DNS는 "이름→IP 변환기"가 아니라 전 지구 규모의 계층적 분산 데이터베이스입니다.
  • 한 번의 getaddrinfo() 호출은 시스템 콜 → 커널 네트워크 스택 → NIC DMA → 대륙 횡단 패킷 → 다단계 위임 → 응답 수신의 여정을 거칩니다.
  • 캐시가 없다면 인터넷은 이미 오래전에 무너졌습니다.
  • 관리되는 네트워크 환경에서 인터넷이 느린 이유는 대부분 DNS 체인 어딘가의 타임아웃입니다.

🌐 여는 말

많은 분들이 DNS를 "도메인을 IP로 바꿔주는 전화번호부" 정도로만 알고 계십니다. 하지만 그 비유로는 DNS의 진짜 모습을 절반도 설명할 수 없는데요, DNS는 사실 전 지구 규모로 분산되어 있고, 계층적이며, 고가용성을 지녔고, 약한 일관성(eventual consistency)을 의도적으로 택한 데이터베이스 시스템입니다.

여러분이 브라우저에 google.com을 치는 순간, 무대 뒤에서는 수십 개의 네트워크 패킷이 대륙을 횡단하고, 여러 개의 서버가 릴레이처럼 질문을 넘기며, 커널의 네트워크 스택이 인터럽트를 발생시키고, NIC(Network Interface Card)의 DMA 버퍼에 데이터가 쓰여지는 일들이 몇십 밀리초 안에 벌어지는데요, 이 모든 과정의 지휘자가 바로 DNS입니다.

이 글에서는 DNS를 애플리케이션 계층의 추상적 개념으로만 보는 것이 아니라, 물리적 패킷이 구리선과 광섬유를 타고 흐르는 하드웨어 레벨까지 내려가서 살펴보겠습니다.



📛 1. 이름과 주소는 왜 나뉘어야 했는가

1.1 IP 주소의 본질

먼저 IP 주소가 무엇인지부터 짚고 가야 하는데요, IPv4 주소는 32비트(4바이트) 정수입니다.

142.250.207.46  →  10001110.11111010.11001111.00101110  →  0x8EFACF2E

142.250.207.46 같은 표기는 인간이 읽기 쉽게 하려고 8비트씩 끊어서 점으로 구분한 것일 뿐, 내부적으로는 0x8EFACF2E라는 하나의 32비트 숫자입니다. IPv6는 이것을 128비트로 확장한 것이고요.

💡 이 32비트 숫자는 "한 컴퓨터"가 아니라 "한 인터페이스"를 식별합니다. 노트북에 이더넷과 WiFi가 각각 다른 IP를 가질 수 있는 이유가 여기 있습니다.

1.2 왜 사람은 숫자가 아니라 이름을 원하는가

인간의 작업 기억은 밀러의 법칙(Miller's Law)에 따라 대략 7±2개의 청크(chunk)만 동시에 유지할 수 있는데요, 142.250.207.46을 외우는 것과 google.com을 외우는 것 사이에는 인지 부하의 차원이 다릅니다.

게다가 IP는 언제든 바뀔 수 있습니다. 서버를 교체하거나 CDN을 바꾸거나 다른 데이터 센터로 마이그레이션하면 IP가 달라지는데, 만약 사람이 IP를 외워서 쓰고 있었다면 세상이 마비됩니다.

그래서 "이름(name)"과 "주소(address)"의 분리라는 시스템 설계 원칙이 등장하는데요, 이건 컴퓨터 과학의 거의 모든 영역에서 반복되는 패턴입니다.

분리 패턴이름 역할주소 역할
포인터 시스템변수명메모리 주소
파일 시스템심볼릭 링크inode 번호
컴파일러심볼레지스터/오프셋
인터넷도메인 이름IP 주소

1.3 초기 인터넷의 HOSTS.TXT — 왜 실패했는가

1970년대 ARPANET 시절에는 DNS가 없었습니다. 대신 스탠포드 연구소(SRI-NIC)HOSTS.TXT라는 파일 하나를 관리했는데요, 전 세계(라기보다 미국의 몇몇 대학)의 모든 호스트 이름과 IP 매핑이 이 파일 하나에 다 들어 있었습니다. 각 기관은 주기적으로 FTP로 이 파일을 다운로드해서 자기 시스템의 /etc/hosts에 덮어쓰는 방식이었죠.

그런데 1980년대 들어 호스트가 기하급수적으로 늘어나면서 이 방식은 세 가지 이유로 붕괴합니다.

  1. 중앙 집중형 병목 — 전 세계가 하나의 파일을 매일 받아야 하니 네트워크 부하가 엄청났습니다.
  2. 이름 충돌 — 두 기관이 서로 모르게 mail이라는 이름을 쓰면 문제가 생깁니다.
  3. 일관성 지연 — IP가 바뀌었는데 전 세계가 그걸 알기까지 며칠이 걸렸죠.

이 세 가지 문제를 한 번에 해결한 게 1983년 Paul Mockapetris가 제안한 RFC 882/883(나중에 RFC 1034/1035로 발전)의 DNS였습니다.



🏛️ 2. DNS의 계층적 분산 구조

2.1 이름 공간은 역트리(inverted tree) 구조입니다

DNS의 이름 공간은 루트(root)를 최상위로 하는 뒤집힌 나무 모양인데요, 여러분이 평소에 쓰는 www.google.com은 사실 생략된 표기이고, 정식 FQDN(Fully Qualified Domain Name)은 www.google.com.입니다.

                    . (root)
                   /    |    \
                com    net    kr
                /       |      \
            google    netflix  co
             /                   \
           www                   naver

맨 끝의 점(.)이 바로 루트를 가리키는 레이블입니다. 이 트리를 해석할 때는 오른쪽에서 왼쪽으로 읽습니다.

📏 각 레이블은 최대 63바이트, 전체 FQDN은 최대 255바이트로 제한됩니다. 이 제한은 뒤에서 볼 DNS 메시지의 압축 기법과 깊이 연관돼 있습니다.

2.2 루트 서버 13개의 진실 — Anycast의 마법

흔히 "루트 DNS 서버는 전 세계에 13개밖에 없다"고 알려져 있는데요, 이건 반은 맞고 반은 틀린 이야기입니다.

구분숫자
논리적 IP 주소13개 (a.root-servers.net ~ m.root-servers.net)
실제 물리 서버1,500개 이상

어떻게 13개의 IP가 1,500개 서버에 대응될까요? 그 비밀이 바로 Anycast라는 네트워크 라우팅 기법입니다.

동일한 IP 주소를 여러 지리적 위치의 서버에 할당해 놓고, BGP(Border Gateway Protocol) 라우팅 테이블이 요청자에게 가장 가까운(홉 수가 적은) 서버로 자동으로 패킷을 보내는 방식인데요, 쉽게 말하면 "서울에서 198.41.0.4(a 루트 서버)로 패킷을 보내면 서울 혹은 도쿄 인스턴스가 응답하고, 뉴욕에서 같은 IP로 보내면 뉴욕 인스턴스가 응답하는" 겁니다.

🛡️ 이 Anycast 덕분에 DNS 루트 서버는 DDoS 공격에 강합니다. 2002년과 2007년의 대규모 루트 서버 공격에도 인터넷이 멈추지 않은 이유가 여기 있습니다.

2.3 권한의 위임(Delegation)

루트 서버는 com, net, kr, jp 같은 TLD(Top-Level Domain)가 누구 관할인지만 알고 있습니다. 실제 google.com의 IP는 모릅니다. 대신 "com에 대해서는 Verisign의 TLD 서버들에게 물어봐라"라고 알려주는데요, 이것이 위임(delegation)입니다.

그래서 DNS 해석은 계층을 따라 내려가는 과정입니다.

[Resolver]  "google.com의 IP 뭐야?"
    │
    ├─▶ [Root]      "com은 Verisign한테 물어봐"
    │
    ├─▶ [com TLD]   "google.com은 ns1.google.com이 관리해"
    │
    └─▶ [google.com 권한 서버]   "142.250.207.46 이야"

이 중 어느 한 단계라도 막히면 전체가 실패합니다.



🚀 3. DNS 쿼리 한 번의 실제 여정

3.1 Stub Resolver — 여러분 OS 안에 있는 작은 녀석

여러분이 브라우저에서 google.com을 입력하면, 가장 먼저 일어나는 일은 애플리케이션이 getaddrinfo()라는 시스템 콜을 호출하는 것입니다.

이 함수는 POSIX 표준 라이브러리가 제공하는 인터페이스로, 내부적으로 Stub Resolver라는 OS의 경량 DNS 클라이언트를 호출하는데요,

  • Linuxglibcnss_dns 모듈 혹은 systemd-resolved
  • WindowsDNS Client Service(Dnscache)
  • macOSmDNSResponder

Stub Resolver는 직접 루트부터 묻지 않습니다. 대신 /etc/resolv.conf(Linux) 또는 네트워크 어댑터 설정(Windows)에 지정된 Recursive Resolver(재귀 해석기)에게 한 번만 묻고 답을 받아옵니다. "나 대신 다 알아내 와"라고 위임하는 거죠.

3.2 Recursive Resolver — 실제로 발품 파는 녀석

Recursive Resolver는 Stub Resolver로부터 "google.com의 A 레코드 알려줘"라는 요청을 받으면, Iterative Query(반복 질의) 과정을 수행합니다.

1단계 — Resolver가 하드코딩으로 가지고 있는 루트 힌트(root hints) 파일에서 루트 서버 IP(가령 198.41.0.4)를 골라 UDP 패킷을 보냅니다. 루트 서버는 답을 주지 않고 "com TLD 서버들은 여기야"라면서 NS 레코드와 Glue 레코드(TLD 서버들의 IP)를 돌려주는데요, 이게 Referral(위임 응답)입니다.

2단계 — Resolver가 받은 com TLD 서버(가령 a.gtld-servers.net)에게 같은 질문을 던집니다. TLD 서버는 "google.com의 권한 서버는 ns1.google.com이야"라고 또 다른 Referral을 돌려줍니다.

3단계 — 비로소 google.com의 권한 서버에게 물어보고, 거기서 실제 A 레코드를 얻어냅니다.

이 세 단계가 순차적으로 일어나는 동안 최소 3번의 왕복(RTT, Round-Trip Time)이 발생하는데요, 각 RTT는 수 밀리초에서 수백 밀리초까지 다양합니다.

3.3 왜 재귀가 아니라 반복인가

"Recursive Resolver라는 이름은 왜 Recursive인데 내부 동작은 Iterative인가?"

이름은 클라이언트 관점의 것입니다. Stub Resolver 입장에서는 한 번 요청하면 모든 답이 재귀적으로 해결되어 돌아오니까 Recursive고, 실제 Resolver가 루트·TLD·권한 서버를 차례로 돌아다니는 행위는 Iterative인 겁니다.



📦 4. DNS 메시지의 내부 구조 — 바이트 단위로 뜯어보기

4.1 왜 UDP 포트 53인가

DNS는 기본적으로 UDP(User Datagram Protocol) 53번 포트를 사용하는데요, 왜 TCP가 아니라 UDP일까요?

이유설명
속도TCP는 3-way handshake로 1.5 RTT를 낭비합니다. UDP는 쏘면 끝.
💾 상태 비유지초당 수십만 건 처리 시 TCP 연결 상태 유지는 리소스 폭발을 부릅니다.
🎯 간결성DNS 질의/응답은 대부분 512바이트 안에 들어갑니다.

다만 응답이 512바이트를 초과하거나, AXFR(존 전송)처럼 대용량 데이터를 주고받아야 할 때는 TCP 53번 포트로 폴백(fallback)합니다. 이 512바이트 제한은 과거 IP MTU 제약과 UDP 단편화 회피를 위한 것인데, 현대에는 EDNS(0)라는 확장으로 4,096바이트까지 UDP로 주고받을 수 있게 되었습니다.

4.2 DNS 메시지 헤더 — 12바이트의 정보 고밀도

DNS 메시지는 고정 12바이트 헤더로 시작하는데요, 구조는 이렇습니다.

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                      ID (16 bits)                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|QR|   Opcode  |AA|TC|RD|RA|   Z    |   RCODE   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    QDCOUNT (16 bits)                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    ANCOUNT (16 bits)                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    NSCOUNT / ARCOUNT                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Transaction ID(16비트)는 질의와 응답을 매칭하는 식별자입니다. 공격자가 이 ID를 추측할 수 있으면 캐시 포이즈닝이 가능하기 때문에, 2008년 Kaminsky 공격 이후로는 UDP 출발 포트까지 무작위화해서 엔트로피를 늘리게 되었습니다.

플래그 비트들 중 특히 중요한 것은:

  • RD (Recursion Desired) — 클라이언트가 재귀 해석을 원한다
  • RA (Recursion Available) — 서버가 재귀 해석을 지원한다
  • AA (Authoritative Answer) — 내가 이 도메인의 권한 서버라서 확신 있게 답한다
  • TC (Truncated) — 응답이 512바이트를 넘어서 잘렸으니 TCP로 다시 물어라

4.3 리소스 레코드의 종류

DNS가 다루는 정보는 전부 RR(Resource Record)이라는 단위로 표현되는데요, 주요 타입은 다음과 같습니다.

타입용도
AIPv4 주소 (32비트)
AAAAIPv6 주소 (128비트), "쿼드A"로 읽습니다
CNAME별칭 — "이 이름은 사실 저 이름이야"
MX메일 교환 서버
NS권한 서버
PTR역방향 조회 (IP → 이름)
SOA존 관리 정보
TXT임의 텍스트 (SPF, DKIM, 도메인 소유 증명 등)

각 RR에는 TTL(Time To Live)이라는 필드가 있는데요, "이 정보를 몇 초 동안 캐시해도 된다"를 의미합니다. CDN 같은 자주 바뀌는 서비스는 TTL을 60초 이하로 짧게 두고, 거의 안 바뀌는 TLD 정보는 며칠 단위로 깁니다.



⚡ 5. 하드웨어 레벨에서 본 DNS 패킷의 여정

5.1 애플리케이션에서 커널까지

getaddrinfo()가 호출되면 어떤 일이 벌어지는지 커널 레벨로 내려가 보겠습니다.

먼저 Stub Resolver는 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP) 시스템 콜로 UDP 소켓을 만듭니다. 이 시스템 콜은 syscall 인스트럭션으로 CPU를 유저 모드에서 커널 모드로 전환시키고, 커널은 내부에 struct sock 구조체를 할당해서 소켓 파일 디스크립터를 반환합니다.

다음으로 sendto() 시스템 콜로 DNS 질의 패킷을 보내는데요, 커널은 이 바이트열을 받아 계층적으로 캡슐화합니다.

[DNS 메시지]
    ↓ UDP 헤더 (8 B) 추가
[UDP 세그먼트]
    ↓ IP 헤더 (20 B, IPv4) 추가, 게이트웨이 MAC 조회
[IP 패킷]
    ↓ 이더넷 헤더 (14 B) 추가
[이더넷 프레임]
    ↓ NIC로 전달

이 캡슐화 과정이 OSI 7계층 모델의 L5→L4→L3→L2 하강 그 자체입니다.

5.2 NIC와 DMA의 역할

패킷이 완성되면 커널은 NIC(Network Interface Card) 드라이버에게 전달하는데요, 현대 NIC는 DMA(Direct Memory Access) 엔진을 가지고 있어서 CPU를 거치지 않고 시스템 메모리에서 직접 패킷을 읽어갑니다.

드라이버는 송신 링 버퍼(TX ring buffer)의 디스크립터에 패킷 버퍼의 물리 주소를 써 넣고, NIC의 MMIO(Memory-Mapped I/O) 레지스터에 "보낼 게 있다"고 신호를 보내는 doorbell write를 수행합니다.

NIC는 DMA로 패킷을 자기 내부 FIFO에 복사한 뒤, PHY 칩을 통해 물리 신호로 변환해서 송출합니다.

  • 구리선(Ethernet) — 맨체스터 부호화 혹은 PAM 변조
  • 광섬유 — On-Off Keying 혹은 더 고차 변조 방식

🌏 이 순간 여러분의 DNS 질의는 전기 신호 혹은 광 펄스가 되어 ISP의 어딘가로 떠나는 겁니다.

5.3 응답의 수신 — 인터럽트와 NAPI

응답이 돌아오면 반대 순서로 일어납니다. NIC가 프레임을 수신하면 DMA로 시스템 메모리의 RX 링 버퍼에 복사하고, 하드웨어 인터럽트(IRQ)를 발생시켜 CPU에게 "일 생겼다"고 알리는데요, 현대 Linux는 인터럽트 폭풍을 막기 위해 NAPI(New API)라는 하이브리드 방식을 씁니다. 첫 인터럽트만 받고 그 후로는 폴링 모드로 전환해서 여러 패킷을 한 번에 처리하는 겁니다.

커널 네트워크 스택은 이더넷 헤더 → IP 헤더 → UDP 헤더를 차례로 벗기고, 페이로드만 남겨서 해당 소켓의 수신 큐에 넣습니다. 이때 epoll 혹은 select 같은 이벤트 통지 메커니즘이 해당 소켓을 대기 중이던 사용자 프로세스를 깨우고, Stub Resolver가 recvfrom()으로 데이터를 읽어갑니다.

이 전체 과정이 보통 수 마이크로초에서 수십 밀리초 안에 끝납니다.



💾 6. 캐싱의 과학 — DNS가 버티는 이유

6.1 다층 캐시 구조

DNS가 전 세계 수십억 기기의 요청을 감당할 수 있는 건 공격적인 다층 캐싱 덕분인데요, 하나의 질의는 다음 계층을 차례로 거칩니다.

[애플리케이션]
    ↓ (브라우저 캐시 — Chrome은 chrome://net-internals/#dns)
[OS Stub Resolver 캐시]
    ↓ (ipconfig /displaydns, resolvectl statistics)
[Recursive Resolver 캐시]
    ↓ (통신사 DNS, 8.8.8.8, 1.1.1.1)
[권한 서버]

TTL은 캐시의 유효 기간을 의미하는데, 레코드가 캐시에 올라온 시점부터 초 단위로 감소하다가 0이 되면 만료됩니다. 만료된 뒤 다시 요청이 오면 Resolver는 상위에 다시 물어봐야 합니다.

6.2 Negative Caching — 없는 것도 캐시합니다

재미있는 건 부정 응답도 캐시한다는 점인데요, 존재하지 않는 도메인에 대해 NXDOMAIN 응답을 받으면 그것도 SOA 레코드의 MINIMUM 필드에 명시된 시간만큼 캐시합니다.

이게 없으면 누군가 asdfasdfasdf.com 같은 이름을 반복 질의할 때마다 전체 해석 체인을 타야 하니 엄청난 낭비가 됩니다.

6.3 캐시의 어두운 면 — Poisoning

캐시는 성능의 원천이지만 보안의 약점이기도 합니다. 만약 공격자가 Resolver의 캐시에 가짜 응답을 주입할 수 있다면, 그 Resolver를 쓰는 모든 사용자가 가짜 사이트로 유도됩니다.

2008년 Dan Kaminsky가 발표한 공격은 이 취약점을 실용적으로 현실화시킨 사건이었는데요, 그 대응으로 Source Port Randomization(UDP 출발 포트 무작위화)과 DNSSEC(DNS Security Extensions)이 널리 퍼지게 되었습니다.



🔒 7. DNSSEC, DoH, DoT — DNS의 현대적 보안

7.1 DNSSEC — 응답의 무결성 서명

DNSSEC은 각 DNS 응답에 디지털 서명을 붙여서 위변조를 탐지하는 체계입니다.

[Root KSK] ─서명─▶ [Root ZSK]
                        │
                        ▼
[TLD KSK] ─서명─▶ [TLD ZSK]
                        │
                        ▼
[Domain KSK] ─서명─▶ [Domain ZSK]

루트의 KSK(Key Signing Key) → TLD의 ZSK(Zone Signing Key) → 도메인의 ZSK로 이어지는 신뢰 체인(chain of trust)을 통해 검증하는데요, RRSIG, DNSKEY, DS, NSEC 같은 새로운 레코드 타입들이 이를 위해 정의되어 있습니다.

⚠️ DNSSEC은 기밀성(confidentiality)은 제공하지 않습니다. 서명만 할 뿐 암호화하지 않아서, 중간자는 여전히 누가 어떤 도메인을 조회하는지 엿볼 수 있습니다.

7.2 DoT와 DoH — 조회 자체를 숨기기

이 기밀성 문제를 해결하기 위해 등장한 것이:

프로토콜포트특징
DoT (DNS over TLS, RFC 7858)TCP 853TLS 세션 안에서 DNS 메시지 교환
DoH (DNS over HTTPS, RFC 8484)TCP 443HTTPS에 DNS를 얹음 — 일반 웹 트래픽과 구분 어려움
DoQ (DNS over QUIC, RFC 9250)UDP 443QUIC의 0-RTT 재개와 멀티플렉싱 활용

DoH는 방화벽이 일반 HTTPS 트래픽과 구분하기 어려워서 검열 우회에 특히 효과적입니다.



🐌 8. 왜 어떤 네트워크에서는 DNS가 느린가

8.1 Split-Horizon DNS

기업이나 교육기관 네트워크는 거의 예외 없이 Split-Horizon DNS(분할 지평 DNS) 구조를 채택하는데요, 이건 같은 도메인에 대해 내부에서 보는 IP와 외부에서 보는 IP가 다르게 응답하는 구성입니다.

외부에서 gitlab.company.com 조회 → 공인 IP (예: 203.0.113.10)
내부에서 gitlab.company.com 조회 → 사설 IP (예: 10.20.30.10)

이 구성을 가능하게 하려면 내부 전용 DNS 서버가 있어야 하고, 단말들은 이 내부 서버를 DNS로 고정해서 써야 합니다.

8.2 포워딩 체인과 타임아웃의 누적

내부 DNS 서버는 자기가 관리하는 도메인(*.company.com 같은)만 직접 답하고, 나머지(google.com, kakao.com 등 외부)는 상위 Forwarder에게 넘기는데요, 이 포워더가 또 다른 내부 보안 장비(프록시 DNS, DNS 방화벽)를 거치는 구조면 지연이 계단식으로 쌓입니다.

[단말] ─▶ [내부 DNS] ─▶ [보안 DNS 방화벽] ─▶ [상위 포워더] ─▶ [공용 DNS] ─▶ [권한 서버]

게다가 상위 포워더 중 하나가 응답이 느리거나 죽어 있다면, Resolver는 기본 타임아웃(보통 5초) 만큼 기다렸다가 다음 후보 서버로 재시도합니다. /etc/resolv.conf에 DNS 서버가 3개 등록돼 있고 첫 번째가 죽어 있으면, 매 질의마다 5초씩 낭비되는 거죠.

🔍 어떤 작업이 유독 40초, 18초 같은 이상한 지연을 보인다면, 거의 확실하게 다중 타임아웃이 누적된 패턴입니다.

8.3 왜 특정 앱 로그인이 더 느리게 느껴지는가

최신 앱의 로그인 과정은 한 번의 DNS 조회로 끝나지 않습니다.

  • 인증 서버 도메인
  • 세션/채팅 중계 서버 도메인
  • 프로필 이미지 CDN 도메인
  • 푸시 서버 도메인

여러 도메인을 연달아 조회해야 하는데, 이 각각이 느린 DNS 체인을 타면 전체 지연이 도메인 개수만큼 배수로 커집니다.

더구나 DNS 해석 후에도 TCP 3-way handshake + TLS handshake(1.2는 2-RTT, 1.3은 1-RTT)가 추가로 필요합니다. DNS에서 이미 몇 초씩 까먹으면 체감 지연이 폭발적으로 느껴지죠.

8.4 집 PC가 빠른 이유

집 공유기는 DHCP가 주는 DNS(보통 ISP DNS나, 공유기 내장 DNS 프록시가 위임하는 8.8.8.8)를 쓰는데요, 이 DNS들은 초거대 캐시와 짧은 네트워크 경로를 가지고 있어서 대부분의 유명 도메인은 이미 캐시돼 있습니다.

응답이 수 밀리초 안에 오니 전체 체감 속도가 다른 겁니다.



🎯 9. 진단과 실전 도구

9.1 dig로 실제 쿼리 뜯어보기

Linux/macOS에서 다음을 실행하면 Resolver 없이 직접 루트부터 내려가는 전체 과정을 관찰할 수 있습니다.

dig +trace google.com

각 단계의 응답 시간까지 찍혀 나오니 어디서 병목이 생기는지 명확히 보입니다. Windows에서는 nslookup이 비슷한 역할을 하지만 기능이 더 제한적이어서, WSL을 통해 dig를 쓰는 걸 권장합니다.

특정 DNS 서버를 명시해서 비교도 가능합니다.

dig @8.8.8.8 google.com       # Google Public DNS
dig @1.1.1.1 google.com       # Cloudflare
dig @<내부 DNS IP> google.com  # 내부망 DNS

내부 DNS와 외부 DNS의 응답 시간을 직접 비교하면, 병목이 어디인지 바로 드러납니다.

9.2 Wireshark로 패킷 단위 관찰

정말 바닥까지 내려가서 보고 싶다면 Wireshark를 켜 놓고 DNS 쿼리를 캡처하면 됩니다.

udp.port == 53

UDP 53번 포트 필터를 걸면 패킷 하나하나의 헤더, 플래그, RR까지 다 들여다볼 수 있는데요, 타임아웃 재시도가 일어나고 있다면 여기서 명확히 보입니다. 동일한 Transaction ID의 질의가 5초 간격으로 반복 발사되는 패턴이 보일 거예요.



🧭 마치며: DNS라는 추상화의 우아함

정리하면, DNS는 단순한 "이름→IP 변환기"가 아니라 전 지구 규모의 계층적 분산 데이터베이스이며, 수십 년에 걸친 엔지니어링 누적의 결정체입니다.

한 번의 getaddrinfo() 호출 뒤에는:

  1. 시스템 콜이 유저-커널 경계를 넘고
  2. 소켓이 생성되고
  3. 커널 네트워크 스택이 패킷을 계층적으로 캡슐화하고
  4. NIC의 DMA가 메모리를 읽어가고
  5. 광신호가 대륙을 건너고
  6. 여러 대륙의 여러 서버가 위임 체인을 따라 릴레이를 하고
  7. 각 계층의 캐시가 미래의 요청을 대비해 결과를 저장하는

일들이 동시에 벌어집니다.

관리되는 네트워크에서 겪는 수십 초의 지연은 사실 이 거대한 시스템 어딘가에서 타임아웃 몇 번이 쌓인 결과일 뿐인데요, 이런 증상을 볼 때마다 "DNS는 어느 계층에서 어떤 형태로 동작하고 있는가"를 떠올릴 수 있다면, 대부분의 네트워크 지연 문제는 스스로 진단할 수 있게 됩니다.

백엔드 개발자로서 DNS를 깊이 이해한다는 건, 단순히 네트워크 엔지니어의 영역을 침범하는 게 아니라, 분산 시스템의 이름 해석과 서비스 디스커버리의 근원을 이해한다는 뜻입니다.

Kubernetes의 CoreDNS, AWS의 Route 53, Service Mesh의 이름 기반 라우팅, Spring Cloud의 서비스 디스커버리 — 이 모든 현대적 인프라의 뿌리에 DNS의 개념이 살아 있습니다.

오늘 글은 여기서 마치겠습니다.



📚 더 읽을거리

  • RFC 1034 — Domain Names: Concepts and Facilities
  • RFC 1035 — Domain Names: Implementation and Specification
  • RFC 7858 — DNS over TLS
  • RFC 8484 — DNS over HTTPS
  • RFC 9250 — DNS over QUIC
  • Computer Networking: A Top-Down Approach — Kurose & Ross
  • Cloudflare Learning — What is DNS?

0개의 댓글