DNS — 이름을 주소로 바꾸는 거대한 전화번호부
사람은 naver.com을 기억하지만, 컴퓨터는 223.130.200.107 같은 IP로만 통신한다. 이 둘 사이를 통역하는 게 **DNS(Domain Name System)**다. 그냥 "이름→IP 표"라고 하면 간단해 보이지만 — 전 세계에 수십억 개의 도메인이 있고 매 순간 바뀐다. 이걸 한 대의 서버가 감당할 수는 없다. DNS의 진짜 묘미는 이 거대한 표를 어떻게 전 세계에 나눠 담고, 빠르게 찾고, 갱신하느냐에 있다. 그 답이 계층적 분산 + 캐싱이다.
표를 나눠 담는다 — 계층적 위임
DNS는 이름을 오른쪽부터 점으로 끊어 트리로 관리한다. www.naver.com.은 — 맨 오른쪽 루트(.) → .com(TLD) → naver.com(권한 서버) → 그 안의 www다. 각 단계는 *"그 아래는 내가 모르니, 저 서버에게 물어봐"*라고 위임한다.
루트 서버는 전 세계에 13개 그룹(잘 알려진 고정 IP)이 있고, 그 아래로 책임이 가지처럼 나뉜다. 덕분에 naver.com의 레코드를 바꿀 권한은 naver만 갖고(자기 권한 서버에서), 루트나 .com 서버는 "저쪽으로 가라"는 이정표만 들고 있으면 된다 — 수십억 도메인이 한 곳에 안 모여도 되는 비결이다. (출처: Cloudflare — Recursive DNS.)
누가 발품을 파나 — 재귀 리졸버와 반복 질의
그럼 이 트리를 누가 따라 내려가 답을 찾을까? 보통 우리 컴퓨터는 직접 안 한다 — *"알아서 찾아서 IP만 알려줘"*라고 재귀 리졸버(ISP나 8.8.8.8 같은)에게 통째로 맡긴다(재귀 질의). 그러면 리졸버가 발품을 판다 — 루트에 물어 .com 서버 주소를 받고, .com에 물어 naver의 권한 서버를 받고, 거기서 최종 IP를 받는다(각 단계는 반복 질의). 즉 *클라이언트→리졸버는 재귀(맡김), 리졸버→서버들은 반복(직접 추적)*이다.
매번 발품 팔면 느리다 — 캐싱과 TTL
이 추적을 매 접속마다 하면 느리고 루트 서버가 터진다. 그래서 모든 단계가 캐싱한다 — 리졸버도, OS도, 브라우저도 한 번 알아낸 답을 잠시 저장한다. 얼마나 저장할지는 각 레코드에 붙은 **TTL**이 정한다. TTL이 길면 캐시가 오래 살아 빠르지만 변경 반영이 느리고, 짧으면 자주 갱신돼 최신성은 좋지만 질의가 잦다 — 전형적인 트레이드오프다(캐시 편의 그 고민과 같다). 자주 보는 레코드 종류는 — A(이름→IPv4), AAAA(이름→IPv6), CNAME(이름→다른 이름의 별칭), MX(메일 서버), TXT(검증·설정), NS(권한 서버 지정)다. (출처: DNS record types.)
실무에서는 — "DNS 바꿨는데 왜 아직 옛날로 가요?" 바로 이 TTL 캐싱 때문이다. 레코드를 바꿔도 전 세계 리졸버 캐시가 옛 TTL이 만료될 때까지 옛 IP를 들고 있어 — 전파(propagation)에 시간이 걸린다. 그래서 서버 이전·도메인 변경 전에는 미리 TTL을 짧게 낮춰 두는 게 정석이다. 또 DNS는 그 자체로 로드밸런싱·장애조치 도구다 — 한 이름에 여러 A 레코드를 두거나(라운드로빈), 지역·상태에 따라 다른 IP를 응답해 트래픽을 분산한다. 디버깅엔
dig·nslookup이 각 단계의 응답을 까 보는 도구다. 그리고 페이지 로딩의 첫 병목이 DNS일 때가 많아, 성능에선 DNS 캐시·프리페치가 의외로 중요하다.
실제로 눈으로 보기
위임을 루트부터 따라간다
dig +trace example.com
# . 518400 IN NS a.root-servers.net. ← ① 루트에게 물었다
# com. 172800 IN NS a.gtld-servers.net. ← ② "com 은 저기로"
# example.com. 172800 IN NS a.iana-servers.net. ← ③ "example.com 은 저기로"
# example.com. 300 IN A 93.184.216.34 ← ④ 드디어 답
아무도 전체 표를 갖고 있지 않다. 루트는 com을 아는 서버만 알려 주고, 그쪽은 example.com을 아는 서버만 알려 준다. 이 위임 구조 덕분에 세상의 모든 도메인을 한 곳에 모으지 않아도 된다.
캐시가 있는지 없는지
dig example.com | grep -E '^example|Query time'
# example.com. 300 IN A 93.184.216.34
# ;; Query time: 42 msec ← 첫 조회 — 발품을 팔았다
dig example.com | grep 'Query time'
# ;; Query time: 0 msec ← 두 번째 — 캐시에서 왔다
dig example.com | grep '^example'
# example.com. 248 IN A ... ← TTL 이 300 에서 248 로 줄어 있다
TTL 이 줄어들고 있으면 캐시에서 온 것이다. 리졸버가 "남은 수명" 을 알려 주기 때문이다. 0이 되면 다시 물어보러 간다.
배포 전에 TTL 을 줄이는 이유
IP 를 바꾸는 작업이 있다면 며칠 전에 TTL 을 낮춰 둔다.
평소 TTL 86400(1일) → 바꿔도 하루 동안 옛 IP 로 가는 사람이 있다
작업 3일 전 TTL 60 → 세상의 캐시가 다 60초짜리로 교체된다
당일 IP 변경 → 1분이면 전부 새 IP 로 온다
작업 후 TTL 복구
TTL 을 당일에 낮추면 소용이 없다 — 이미 퍼진 캐시는 옛 TTL 로 살아 있기 때문이다. 이 순서를 모르면 "TTL 을 60으로 했는데 왜 아직 옛 서버로 오나" 라는 상황을 만난다.
특정 서버에 직접 물어 캐시를 우회한다
dig @1.1.1.1 example.com +short # 공개 리졸버에
dig @a.iana-servers.net example.com +short # 권한 서버에 직접
# 둘이 다르면 → 전파가 아직 안 끝난 것이다
"나는 바뀐 게 보이는데 사용자는 안 보인다" 는 상황을 이렇게 확인한다. 권한 서버는 이미 바뀌었고 리졸버 캐시만 남은 것이면 기다리면 되고, 권한 서버부터 옛 값이면 레코드를 안 바꾼 것이다.
어느 레코드가 무엇을 하나
dig example.com A +short # 이름 → IPv4
dig example.com AAAA +short # 이름 → IPv6
dig example.com MX +short # 메일 서버
dig example.com NS +short # 이 도메인을 관리하는 서버
dig example.com CNAME +short # 다른 이름의 별명
dig -x 93.184.216.34 +short # 역방향 — IP → 이름
CNAME 은 루트 도메인(example.com)에 못 붙인다 — 그 자리에 NS·SOA 같은 다른 레코드가 반드시 있어야 하는데 CNAME 은 다른 레코드와 공존할 수 없기 때문이다. www에는 되고 루트에는 안 되는 이유가 이것이고, 그래서 각 서비스가 대체 수단(ALIAS·ANAME)을 따로 제공한다.
정리하면, DNS는 이름을 IP로 바꾸는데 — 그 거대한 표를 계층적으로 위임해 나눠 담고(루트→TLD→권한 서버), *재귀 리졸버*가 발품을 팔아 추적하며(클라이언트는 맡기고, 리졸버는 반복 질의로 추적), 각 단계의 캐싱과 TTL로 빠르고 확장 가능하게 만든다. 그 TTL이 변경 전파 지연의 원인이자 로드밸런싱·장애조치의 지렛대이기도 하다. 이름이 IP로 풀리고 TCP 연결까지 섰으면 — 이제 그 위로 실제 요청과 응답을 주고받을 차례다. 그게 HTTP다.