브라우저에 example.com을 치고 엔터를 누른다. 페이지가 뜬다. 200밀리초 정도 걸렸다. 생각조차 안 했다.
그 200밀리초 동안 쿼리는 서버 네 곳을 거쳤을 수도 있고, 1983년에 설계된 계층 구조를 지나왔고, 대부분의 쿼리가 목적지에 도달하지도 않을 만큼 공격적인 캐싱 시스템에 의존했다. 전부 안 보인다. 키를 누르는 순간과 화면의 첫 픽셀 사이에 일어난다.
쿼리는 생각보다 가까이서 시작된다
네트워크에 뭔가 닿기 전에, 컴퓨터는 싸게 얻을 수 있는 답부터 확인한다.
브라우저가 먼저다. Chrome과 Firefox는 최근 조회 결과를 담은 작은 캐시를 따로 둔다. 거기 없으면 브라우저는 운영체제에 묻는다. 보통 호스트 이름을 주소로 바꿔 주는 표준 라이브러리 함수 getaddrinfo()를 통해서다. (DNS-over-HTTPS를 켠 일부 브라우저는 OS를 건너뛰고 자기 리졸버와 직접 대화한다.)
OS는 먼저 /etc/hosts(윈도우는 C:\Windows\System32\drivers\etc\hosts)를 본다. 호스트 이름을 IP 주소에 바로 대응시키는 텍스트 파일이다. 인터넷 전체의 호스트 목록이 내려받는 텍스트 파일 하나였던 시절의 유물이다. 기본 설정에서는 DNS보다 먼저 참조된다. 로컬 개발에 유용한 이유이자, 멀웨어가 가끔 이 파일을 고치는 이유다.
그다음은 OS 캐시가 있다면 그 캐시다. 윈도우의 DNS Client 서비스, macOS의 mDNSResponder, 많은 리눅스 배포판의 systemd-resolved가 그 역할을 한다. 이 이름을 최근에 조회했나? 캐시된 답이 아직 TTL 안에 있나? 그렇다면 끝. 네트워크 요청 없음. 1밀리초 미만.
로컬 어디에도 답이 없으면 진짜 DNS가 시작된다.
내 컴퓨터는 해석을 끝까지 직접 하지 않는다. 위임한다. OS에 내장된 가벼운 DNS 클라이언트인 스텁 리졸버가 쿼리를 받아 재귀 리졸버에 넘긴다. 어떤 재귀 리졸버? 설정된 것. 공유기, ISP의 리졸버, 아니면 8.8.8.8이나 1.1.1.1 같은 공용 리졸버.
스텁이 작은 UDP 패킷을 보낸다. “example.com의 A 레코드가 뭐야?” 보통 같은 순간에 AAAA(IPv6) 레코드를 묻는 패킷도 하나 더 보낸다. 그리고 기다린다. 이후 모든 일은 재귀 리졸버 쪽에서 일어난다.
재귀 리졸버의 캐시
재귀 리졸버도 자기 캐시부터 확인한다. 8.8.8.8 같은 바쁜 공용 리졸버는 엄청나게 많은 사용자를 상대하니 캐시가 늘 따뜻하다. 누군가 최근에 example.com을 물어봤으면 답이 이미 있다. 응답이 돌아간다. 걸린 시간은 리졸버까지 왕복하는 시간, 대개 몇 밀리초다.
대부분의 쿼리가 여기서 끝난다. 캐싱 레이어가 DNS 트래픽의 대다수를 흡수한다. 인기 있는 도메인에 접속하면 재귀 리졸버에 캐시된 답이 거의 확실히 있다.
근데 없다고 하자. 캐시가 비었다. 이제 리졸버가 처음부터 답을 찾으러 가야 한다.
트리 걷기
DNS는 계층 구조다. 루트 존이 꼭대기. TLD가 그 아래(.com, .org, .net). 개별 도메인의 권한 있는 네임서버가 그 아래. 재귀 리졸버가 이 트리를 위에서 아래로 걷는다.
첫 번째 정거장: 루트. 리졸버가 루트 서버에 묻는다. “example.com의 A 레코드가 뭐야?” 루트는 모른다 — 개별 도메인 레코드를 저장하지 않는다. 하지만 .com을 누가 관리하는지는 안다. 그래서 리퍼럴을 돌려준다. “이 네임서버들에 물어봐.”
이름이 붙은 루트 서버는 A부터 M까지 13개이고, 독립된 운영 기관 12곳이 맡는다. 실제로는 2,000개 가까운 물리적 인스턴스(root-servers.org 집계로 2025년 말 1,954개)가 애니캐스트로 전 세계에 흩어져 있다 — 같은 IP 주소가 위치에 따라 다른 머신에서 서빙되는 라우팅 기법. 리졸버가 “루트 서버”와 대화할 때, 가장 가까운 물리적 인스턴스와 대화하는 것이다. 리졸버에는 루트 주소가 처음부터 내장돼 있어서(거의 안 바뀐다) 이 단계는 빠르다. 왕복 한 번. 실제로 돌아가는 리졸버는 .com 리퍼럴을 최대 이틀 동안 캐시해 두므로 루트에 물을 일이 드물다.
두 번째 정거장: TLD. 리졸버가 .com 서버에 묻는다. 같은 패턴. 최종 답은 없지만 example.com의 권한 있는 네임서버를 안다. 리퍼럴을 돌려준다. 왕복 한 번 더.
세 번째 정거장: 권한 있는 네임서버. 리졸버가 직접 묻는다. 이 서버가 실제 레코드를 갖고 있다. IP 주소로 응답한다. 마지막 왕복.
리졸버가 답을 레코드의 TTL(Time To Live)에 따라 캐시하고, 스텁 리졸버에 보내고, 스텁은 애플리케이션에 보내고, 애플리케이션은 그 IP로 연결을 연다(TCP, QUIC이면 UDP).
완전히 캐시가 비어있으면 왕복 세 번. 실제로는 TLD 리퍼럴이 거의 항상 캐시되어 있어서, 흔한 TLD 아래 새 도메인의 “콜드” 쿼리는 보통 한두 번이면 된다.
50밀리초 예산
왕복 한 번의 비용은 거리가 정한다. 가까운 애니캐스트 인스턴스까지는 몇 밀리초, 다른 대륙의 권한 서버까지는 100밀리초가 넘기도 한다. 캐시가 완전히 빈 조회는 수백 밀리초 초반까지 갈 수 있다. 일부가 캐시된 조회(흔한 경우)는 보통 수십 밀리초다. 로컬 캐시에 맞으면 1밀리초 미만이다. 제목의 50은 여기서 나왔다. 흔한 예산이지 법칙이 아니다.
TTL이 중요한 이유다. 300초 TTL(5분)이면 리졸버가 5분마다 다시 쿼리한다. 86400초 TTL(24시간)이면 하루에 한 번. 짧은 TTL은 더 신선한 데이터지만 만료된 캐시에서 더 느리다. 긴 TTL은 더 빠른 응답이지만 레코드를 바꿨을 때 전파가 느리다.
“DNS 전파에 24-48시간 걸린다”는 말의 실제 의미: 전 세계 캐시가 TTL이 만료될 때까지 오래된 답을 들고 있다. 전파 메커니즘 같은 건 없다. 오래된 캐시가 각기 다른 시간에 만료될 뿐이다. 48시간이라는 숫자도 괜히 나온 게 아니다. .com 레지스트리는 NS 레코드를 이틀짜리 TTL로 게시하므로, 네임서버를 바꾸면 모든 곳에 반영되기까지 그만큼 걸릴 수 있다.
잘못될 수 있는 것
각 단계에서 실패할 수 있다.
NXDOMAIN. 도메인이 존재하지 않는다. 권한 있는 서버가 그렇다고 말한다. 깔끔한 실패.
SERVFAIL. 리졸버가 답을 얻으려 했는데 못 했다. 권한 있는 서버가 다운이거나, DNSSEC 검증이 실패했거나, 네트워크 문제일 수 있다. SERVFAIL은 “뭔가 깨졌다”의 범용 응답이고, 디버깅하려면 각 홉에서 쿼리를 추적해야 한다.
타임아웃. 리졸버가 쿼리를 보냈는데 아무것도 안 왔다. 몇 초 뒤 재시도하는데, 보통 다른 서버로 보낸다. 전부 타임아웃이면 쿼리가 실패한다. 사용자는 브라우저 에러 페이지를 본다.
캐시 포이즈닝. 공격자가 리졸버의 캐시에 가짜 응답을 주입한다. 그 도메인을 쿼리하는 모두가 가짜 답을 받는다. DNSSEC가 방지하는 것이다 — 낮은 채택률이 답답한 이유이기도 하다.
보이지 않는 기계
DNS는 인터넷 핵심에서 아직 돌아가는 가장 오래된 프로토콜 중 하나다. 네트워크 전체가 문서 하나에 매핑되던 시절에 설계됐다. 지금은 온라인에서 일어나는 거의 모든 일의 조회를 떠맡고 있다.
대부분의 개발자가 하루에 수백 번 쓰면서 한 번도 의식하지 않는다. URL이 들어가고, 페이지가 뜨고, 그 사이의 50밀리초는 없는 것이나 마찬가지다.
하지만 존재한다. 다음에 페이지가 뚜렷한 이유 없이 느리게 뜨면 — DNS부터 확인해보자. 보이지 않는 기계가 버벅거리고 있을 수 있다.