Ở bài về phân giải DNS tôi đo một lần tra tên như một hộp đen: gửi truy vấn, nhận địa chỉ. Nhưng khi không có gì trong đệm — một tra cứu nguội — bên trong hộp đen đó là cả một hành trình đi bộ xuống cây phân cấp DNS: hỏi root, rồi hỏi máy chủ TLD, rồi hỏi máy chủ thẩm quyền. Mỗi chặng là một vòng khứ hồi. Bài này dựng một cây DNS thu nhỏ của riêng tôi — root, TLD, và thẩm quyền, mỗi cái một máy chủ trong container — rồi đo chính xác một tra cứu nguội đi bao nhiêu vòng, và caching cắt bớt được bao nhiêu.
Đi bộ xuống cây phân cấp
Tôi dựng ba máy chủ DNS trên ba địa chỉ trong container: một root (10.10.0.2), một máy chủ TLD cho .test (10.10.0.3), và một máy chủ thẩm quyền cho web.test (10.10.0.4). Rồi tôi viết một resolver đệ quy bắt đầu từ root và đi theo các chỉ dẫn (referral). Với tên web.test, trình tự là:
- Hỏi root: "ai giữ
.test?" → root không biếtweb.testlà gì, nhưng biết ai quản.test, nên nó trả về một referral: "hỏi máy chủ TLD ở 10.10.0.3". - Hỏi TLD: "ai giữ
web.test?" → TLD trả referral: "hỏi máy chủ thẩm quyền ở 10.10.0.4". - Hỏi thẩm quyền: "
web.testlà gì?" → trả về câu trả lời thật:A = 10.1.2.3.
Ba câu hỏi, ba máy chủ khác nhau, ba vòng khứ hồi. Một chi tiết quan trọng: mỗi referral kèm địa chỉ IP của máy chủ kế tiếp (trong phần "additional", gọi là bản ghi glue), chứ không chỉ đưa cái tên. Nếu chỉ đưa tên máy chủ mà không đưa địa chỉ, resolver lại phải đi tra cái tên đó trước — một vòng lặp gà-và-trứng mà glue sinh ra để chặt đứt.
Đo: nguội ba vòng, ấm không vòng
Với mỗi máy chủ đặt độ trễ 20 mili giây, tôi cho một resolver (đệm chung) tra ba tên liên tiếp và đếm số vòng:
| Truy vấn | Số vòng | Thời gian |
|---|---|---|
web.test (nguội, đệm rỗng) |
3 | ~75 ms |
web.test (lần 2, đã đệm) |
0 | ~0 ms |
shop.test (cùng .test) |
2 | ~48 ms |
Ba dòng này là toàn bộ câu chuyện của caching DNS. Lần đầu tra web.test tốn 3 vòng — đúng bằng độ sâu của chuỗi ủy quyền (root → TLD → thẩm quyền). Lần thứ hai tra cùng tên đó tốn 0 vòng: câu trả lời đã nằm trong đệm, resolver trả thẳng, không hỏi ai.
Dòng thứ ba mới thú vị. shop.test là một tên khác, chưa từng tra, nhưng nó chỉ tốn 2 vòng thay vì 3. Lý do: khi tra web.test, resolver đã đệm luôn cái referral từ root (".test do máy chủ TLD 10.10.0.3 quản"). Nên với shop.test, nó bỏ qua hẳn root, đi thẳng vào TLD, rồi tới thẩm quyền. Root được hỏi một lần rồi thôi.
Đây chính là lý do hệ thống DNS toàn cầu không sập dưới sức nặng của hàng nghìn tỷ truy vấn mỗi ngày: máy chủ root và TLD hầu như không bao giờ bị hỏi tới, vì mọi resolver trên đời đã đệm sẵn "ai giữ .com, ai giữ .vn, ai giữ .test" từ lâu, với TTL rất dài. Chuỗi ủy quyền được đệm còn đắt giá hơn cả câu trả lời cuối, vì nó phục vụ vô số tên cùng nhánh.
Một lần tôi đo hớ về nhãn ủy quyền
Phép đo shop.test = 2 vòng không ra ngay từ đầu. Lần chạy đầu tiên, máy chủ TLD của tôi gắn nhãn cho referral của nó bằng vùng rộng test. thay vì đúng vùng được ủy quyền web.test.. Nghe qua thì vô hại, nhưng nó phá caching một cách tinh vi.
Resolver của tôi đệm các ủy quyền theo cái tên vùng ghi trên referral. Root nói "test. → máy chủ TLD (10.10.0.3)", resolver đệm đúng vậy. Nhưng rồi TLD (do gắn nhãn sai) nói "test. → máy chủ thẩm quyền (10.10.0.4)", và câu này ghi đè lên điều root vừa dặn, vì cùng khóa test.. Kết quả: lần hỏi shop.test kế đó, resolver tra đệm thấy "test. → 10.10.0.4" và nhảy thẳng tới máy chủ thẩm quyền, đo ra 1 vòng thay vì 2 — bỏ qua cả root lẫn TLD. Trong cây thu nhỏ của tôi nó vẫn ra câu trả lời (vì máy chủ thẩm quyền trả lời mọi tên), nhưng trong một cây thật, đó là gửi câu hỏi tới nhầm tầng, và sẽ hỏng.
Cách sửa: mỗi referral phải mang đúng tên vùng nó ủy quyền — TLD ủy quyền web.test. thì nhãn phải là web.test., không phải test.. Bài học: trong DNS, cái tên trên bản ghi ủy quyền chính là thứ nói "referral này dành cho vùng nào". Gắn nhãn rộng hơn thực tế là mời caching định tuyến sai. Đây là kiểu lỗi mà một phép đo cẩu thả sẽ không bắt được — con số "1 vòng" trông còn nhanh hơn, dễ bị lầm là tốt.
Vì sao điều này quan trọng khi lập trình
Hệ quả đầu tiên là độ trễ của lần chạm nguội. Mỗi vòng trong bảng trên là một khứ hồi mạng, và trên Internet thật, các máy chủ root/TLD/thẩm quyền có thể nằm rải rác khắp thế giới. Một tra cứu nguội tới một tên miền mới có thể tốn ba tới bốn khứ hồi, mỗi cái hàng chục mili giây — cộng lại thành hàng trăm mili giây trước khi bắt tay TCP kịp bắt đầu. Đây là một phần lý do lần đầu truy cập một trang lạ luôn chậm hơn hẳn lần sau, và là lý do các trình duyệt "prefetch DNS" cho những tên miền chúng đoán bạn sắp cần.
Hệ quả thứ hai là hiểu đúng vai trò của resolver đệ quy. Máy của bạn không tự đi bộ xuống cây phân cấp — nó gửi một câu hỏi cho một resolver đệ quy (ghi trong /etc/resolv.conf, thường là của nhà mạng hoặc một dịch vụ như 1.1.1.1), và chính resolver đó làm toàn bộ hành trình root → TLD → thẩm quyền thay bạn, rồi trả về một câu trả lời gọn. Nhờ resolver này phục vụ hàng triệu người và đệm chung, phần lớn truy vấn của bạn trúng đệm của nó và trả về trong một khứ hồi duy nhất tới resolver — không phải ba. Chất lượng và vị trí của resolver đệ quy vì thế ảnh hưởng trực tiếp tới độ trễ mọi kết nối của bạn.
Hệ quả thứ ba nối với bài về TTL: độ sâu chuỗi ủy quyền và TTL cùng quyết định chi phí DNS thật. Một tên sâu (nhiều tầng ủy quyền) nguội thì tốn nhiều vòng; nhưng nhờ TTL dài ở các tầng trên, phần đắt đó hiếm khi phải trả lại. Con số mang theo: một tra cứu nguội đi số vòng bằng độ sâu chuỗi ủy quyền — root, TLD, thẩm quyền là ba; đệm chuỗi đó khiến các tên cùng nhánh chỉ tốn phần dưới, và khiến root gần như không bao giờ bị hỏi tới. Hiểu điều này là hiểu vì sao DNS vừa phân tán khắp thế giới vừa nhanh, và vì sao lần chạm đầu tiên luôn là lần đắt nhất.
Thử ba mươi giây
Chạy dig +trace <một tên miền> — cờ +trace bảo dig tự đi bộ xuống cây phân cấp thay vì hỏi resolver đệ quy, nên bạn thấy từng chặng một: nó hỏi root, nhận referral tới TLD, hỏi TLD, nhận referral tới thẩm quyền, rồi mới nhận câu trả lời. Đếm số chặng chính là số vòng của một tra cứu nguội. So với dig <tên> thường (hỏi thẳng resolver đệm) trả về gần như tức thì, bạn sẽ thấy đúng khác biệt giữa "nguội" và "ấm" mà bài này đo.