Ở bài trước tôi đo được một điều bất ngờ: hệ điều hành không tự cache DNS, mỗi lần tra tên là một lượt ra mạng mới. Nhưng trong thực tế, việc nhớ kết quả DNS có xảy ra — chỉ là ở một trình phân giải đệm riêng, và nó bị chi phối bởi một con số duy nhất: TTL. Bài này dựng một trình phân giải đệm (dnsmasq) trước một máy chủ thẩm quyền của riêng tôi, rồi đo từng giây xem một bản ghi được giữ trong đệm bao lâu, và điều gì xảy ra khi tôi đổi bản ghi ở nguồn.
TTL: bản ghi được phép nhớ bao lâu
Mỗi bản ghi DNS đi kèm một con số TTL (Time To Live), tính bằng giây, do vùng (zone) thẩm quyền đặt. Nó là một giấy phép: một trình phân giải đệm được phép giữ bản ghi tối đa TTL giây, sau đó phải quên đi và hỏi lại nguồn. Đây là cơ chế cân bằng giữa hai thứ đối nghịch — nhớ lâu thì đỡ tải truy vấn nhưng chậm cập nhật khi bản ghi đổi; nhớ ngắn thì cập nhật nhanh nhưng dồn nhiều truy vấn lên nguồn.
Để đo, tôi đặt máy chủ thẩm quyền của mình trả về bản ghi web.test → 10.1.2.3 với TTL 5 giây, đặt dnsmasq làm lớp đệm phía trước, rồi gửi một truy vấn mỗi giây và in ra địa chỉ nhận được cùng TTL còn lại trong câu trả lời.
Đệm giữ bản ghi đúng bằng TTL
Kết quả vẽ ra một nhịp đập rất đều:
t=0s 10.1.2.3 TTL còn lại 5s (miss: nạp mới từ nguồn, ~1 ms)
t=1s 10.1.2.3 TTL còn lại 4s (hit: trả từ đệm, ~0,2 ms)
t=2s 10.1.2.3 TTL còn lại 3s (hit)
t=3s 10.1.2.3 TTL còn lại 2s (hit)
t=4s 10.1.2.3 TTL còn lại 1s (hit)
t=5s 10.1.2.3 TTL còn lại 5s (miss: hết hạn, nạp lại)
TTL còn lại đếm ngược 5 → 4 → 3 → 2 → 1, rồi ở giây thứ 5 nó nhảy về 5 — dấu hiệu đệm đã hết hạn và đi hỏi nguồn lần nữa. Đệm giữ bản ghi đúng 5 giây, không hơn không kém. Chạy lại với TTL 2 giây thì nhịp đổi thành hỏi nguồn mỗi 2 giây. Thời gian đệm giữ bám chính xác theo TTL.
Chú ý cả cột thời gian đáp: những lần trúng đệm trả về trong khoảng 0,2 mili giây (đệm trả thẳng, không đi đâu cả), còn những lần trượt đệm mất khoảng 1 mili giây vì phải đi lên nguồn. Trên đường thật, khác biệt này là giữa "trả tức thì từ RAM" và "một vòng khứ hồi mạng tới máy chủ DNS" — chính là giá trị của caching.
Đổi bản ghi: hiệu lực bị trễ đúng một TTL
Đây là phần thực tế nhất. Ngay sau truy vấn ở giây thứ 5, tôi đổi bản ghi ở nguồn từ 10.1.2.3 sang 10.1.2.99 — như thể bạn vừa cập nhật bản ghi DNS trỏ tên miền sang một máy chủ mới. Rồi tiếp tục hỏi mỗi giây:
t=6s 10.1.2.3 TTL còn lại 4s (hit: VẪN giá trị CŨ)
t=7s 10.1.2.3 TTL còn lại 3s (hit: vẫn cũ)
t=8s 10.1.2.3 TTL còn lại 2s (hit: vẫn cũ)
t=9s 10.1.2.3 TTL còn lại 1s (hit: vẫn cũ)
t=10s 10.1.2.99 TTL còn lại 5s (miss: giờ mới thấy giá trị MỚI)
Tôi đổi bản ghi ở giây thứ 5, nhưng phải tới giây thứ 10 giá trị mới mới xuất hiện — trễ đúng một TTL. Trong suốt khoảng đó, đệm vẫn phục vụ giá trị cũ một cách hoàn toàn "đúng luật": nó có một bản sao còn hạn, nên không việc gì phải hỏi lại nguồn. Nguồn đã đổi, nhưng thế giới bên ngoài không thấy cho tới khi bản sao đệm hết hạn.
Đây chính là gốc rễ của câu than quen thuộc "tôi đổi DNS rồi mà chưa ăn". Gần như luôn luôn, bản ghi đã đổi đúng ở nguồn; chỉ là một trình phân giải đệm nào đó trên đường (của máy bạn, của nhà mạng, của trình duyệt) vẫn đang giữ bản sao cũ cho hết TTL của nó.
Một lần tôi đo hớ về "TTL"
Lúc mới bắt đầu, tôi định đọc "TTL của bản ghi" một cách đơn giản: gửi một truy vấn, đọc trường TTL trong câu trả lời. Nhưng mỗi lần hỏi lại ra một số khác nhau — lúc 5, lúc 3, lúc 1 — và tôi bối rối không biết TTL "thật" là bao nhiêu.
Nguyên nhân: một trình phân giải đệm không trả về TTL gốc của bản ghi, mà trả về TTL còn lại — thời gian bản sao trong đệm còn sống, đang đếm ngược về 0. Nếu tôi tình cờ hỏi khi bản ghi đã nằm trong đệm 4 giây, tôi nhận TTL = 1; hỏi ngay sau một lần trượt đệm thì mới thấy con số gốc TTL = 5. Muốn biết TTL gốc của một bản ghi, phải bắt đúng một lần trượt đệm, hoặc hỏi thẳng máy chủ thẩm quyền (nơi luôn trả TTL gốc). Đọc TTL từ một câu trả lời đệm ngẫu nhiên là đọc "còn lại bao lâu", không phải "được nhớ bao lâu".
Bài học đo lường: cùng một trường số nhưng mang ý nghĩa khác nhau tùy bạn hỏi ai. Con số TTL từ nguồn là hạn cấp phát; con số TTL từ một lớp đệm là hạn còn lại. Lẫn hai cái là rút ra kết luận sai về việc bản ghi thực sự được cấu hình thế nào.
Vì sao điều này quan trọng khi lập trình
Hệ quả vận hành quan trọng nhất là lập kế hoạch cho thay đổi DNS. Trước khi chuyển một tên miền sang máy chủ mới (di dời, đổi nhà cung cấp, chuyển IP nổi khi sự cố), bạn nên hạ TTL xuống thấp trước vài giờ — để khi thực hiện cú đổi, các đệm khắp nơi hết hạn nhanh và giá trị mới lan đi trong vài phút thay vì vài giờ. Xong việc thì nâng TTL lại để giảm tải. Không biết cơ chế này thì một cú chuyển máy chủ có thể để lại một phần người dùng đi vào IP cũ suốt cả ngày.
Hệ quả thứ hai là các lớp đệm chồng lên nhau. Trình duyệt có đệm riêng, hệ điều hành (nếu có resolver đệm) có đệm riêng, resolver của nhà mạng có đệm riêng — mỗi lớp giữ bản ghi theo TTL của chính nó. Thời gian tệ nhất để một thay đổi lan tới một người dùng cụ thể có thể là tổng các quãng đệm dọc đường, chứ không chỉ một TTL. Đây là lý do "xóa cache DNS trình duyệt" đôi khi giúp, nhưng đôi khi không, vì lớp gây kẹt nằm ở resolver nhà mạng ngoài tầm bạn.
Hệ quả thứ ba là đánh đổi tải. TTL thấp (vài giây) cho khả năng đổi nhanh và chuyển đổi dự phòng mượt, nhưng đổi lại là mỗi client hỏi lại rất thường xuyên, dồn tải truy vấn và thêm độ trễ trượt-đệm vào nhiều kết nối hơn. Các dịch vụ dùng DNS để cân bằng tải hoặc failover (TTL 30–60 giây là phổ biến) phải chấp nhận cái giá truy vấn đó. Con số mang theo: một bản ghi được đệm đúng bằng TTL giây, và mọi thay đổi ở nguồn chỉ có hiệu lực sau khi bản sao đệm hết hạn — chọn TTL là chọn trước sự đánh đổi giữa tốc độ cập nhật và tải truy vấn, và biết điều đó là biết vì sao thay đổi DNS không bao giờ tức thì.
Thử ba mươi giây
Tra một tên miền hai lần liên tiếp bằng dig <tên> và nhìn cột TTL trong phần ANSWER — bạn sẽ thấy nó giảm dần giữa hai lần hỏi, đúng như phép đo trên: đó là TTL còn lại trong đệm resolver của bạn, đang đếm ngược. Đợi tới khi nó về 0 rồi hỏi lại, con số sẽ nhảy về giá trị gốc — khoảnh khắc đệm hết hạn và đi hỏi nguồn. Muốn thấy TTL gốc mà zone đặt, thêm +trace hoặc hỏi thẳng máy chủ thẩm quyền của tên miền đó.