Learning Platform
Глоссарий Troubleshooting
Урок 10.04 · 18 мин
Средний
DNSCachingTTLPropagation

DNS-кэширование и TTL — слои кэшей и негативное кэширование

DNS — это самая популярная сеть в мире. По данным Cloudflare и Google, в день делается ~10 триллионов DNS-запросов глобально. Если бы каждый запрос проходил полный путь через root -> TLD -> authoritative, root-серверы развалились бы за минуты.

DNS работает в таком масштабе только благодаря кэшированию. На каждом уровне (от твоего браузера до recursive resolver’а) данные кэшируются на сроки, указанные в TTL (Time-To-Live). Это уменьшает реальную нагрузку на authoritative-серверы в тысячи раз.

В этом уроке разберём:

  1. Слои кэшей на пути от приложения до интернета.
  2. Как TTL влияет на propagation изменений.
  3. Negative caching — кэширование «не существует».
  4. Что такое stale cache и как с этим жить.
  5. Cache poisoning и защиты.

Слои кэшей

Между твоим приложением и authoritative-сервером есть от 3 до 6 слоёв кэша:

Стек DNS-кэшей -- от приложения до интернета
App cacheВнутри приложения. JVM кэширует DNS forever по дефолту. Browser Chrome -- пара минут. Python requests/urllib3 обычно не кэширует. Подвержен сильным различиям
OS stub resolver cacheСистемный DNS-кэш. macOS: mDNSResponder. Linux: systemd-resolved или nscd или dnsmasq. Windows: DNS Client service. Соблюдает TTL обычно неплохо
Local DNS serverЕсли у тебя дома роутер делает caching DNS (часто), или в корп. сети -- DNS-сервер компании. Кэш на минуты-часы
ISP recursive resolverDNS-сервер твоего провайдера или публичный (8.8.8.8). Самый 'жирный' кэш -- гигабайты записей, всё что было запрошено за TTL
Anycast DNS edgeЕсли используешь публичный DNS (8.8.8.8 или 1.1.1.1) -- он anycast-distributed по миру. Каждый PoP имеет свой кэш, синхронизация между ними может быть с задержкой
Authoritative serverБез кэша -- первоисточник. Изменение записи здесь -- это где меняется реальная истина. Но клиенты увидят это только когда выгребут до конца все TTL в кэшах выше

Реальное распределение нагрузки: на authoritative-сервер приходит ~1% от того, что генерируют клиенты. Всё остальное обслуживается из кэшей.


TTL в деталях

TTL (Time-To-Live) — это unsigned 32-битное число в секундах, указанное в каждой DNS-записи. Когда resolver кэширует запись, он записывает её с этим TTL. По истечении — запись удаляется из кэша.

Типичные значения TTL:

  • Short (30-300 сек): для часто меняющихся сервисов (CDN, A/B-тестирование, blue-green deployments).
  • Medium (1-24 часа): для большинства веб-сайтов. Хороший баланс между гибкостью и нагрузкой.
  • Long (1-7 дней): для NS-записей родительских зон (TLD), почти не меняются.
# Посмотри TTL на разных типах записей:
dig example.com SOA +noall +answer
# 86400 -- TTL SOA

dig example.com NS +noall +answer
# 86400 -- TTL NS (длинный)

dig www.github.com A +noall +answer
# 60 -- TTL A (короткий, для гибкости)

dig 8.8.8.8 -x +noall +answer
# Различные TTL -- зависит от провайдера

Чем короче TTL:

  • Быстрая propagation при изменениях.
  • Больше нагрузка на authoritative-серверы.
  • Дольше резолв при cold cache.

Чем длиннее TTL:

  • Меньше нагрузка на сеть.
  • Быстрее warm cache hits.
  • Долгая propagation изменений.

Стандартная стратегия:

  • Обычные веб-сайты: 3600 (1 час) или 14400 (4 часа).
  • Перед миграцией IP: понизить до 60-300 за день-два.
  • После миграции: вернуть к нормальному уровню.

Как TTL «декрементируется» по дороге

Важный нюанс: когда recursive resolver кэширует запись с TTL=3600, и спустя 1000 секунд клиент его запрашивает, resolver отдаёт запись с TTL=2600. Это и есть остаток времени, в течение которого запись валидна.

Каждый последующий слой кэша делает то же самое — отвечает остаток TTL. Это значит, что если у тебя серия кэшей по пути, реальное «время жизни» записи определяется самым первым и долгим кэшем.

# Демонстрация декремента TTL:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
dig +noall +answer www.example.com A
# 3600

# Подожди 30 секунд и запроси снова:
sleep 30
dig +noall +answer www.example.com A
# 3570  -- TTL уменьшился

# Подожди ещё:
sleep 30
dig +noall +answer www.example.com A
# 3540

Negative caching — кэш «не существует»

Что происходит, если ты запрашиваешь несуществующий домен? Resolver сходит до authoritative-сервера, получит NXDOMAIN, и закэширует этот факт тоже. Иначе при следующем запросе того же имени он опять пойдёт через всю иерархию.

Длительность негативного кэша определяется Minimum TTL в SOA-записи. По RFC 2308 это поле использовано именно для этого: «как долго кэшировать NXDOMAIN и NODATA для этой зоны».

# Посмотри SOA и Minimum TTL:
dig example.com SOA
# В SOA: NS hostmaster serial refresh retry expire MINIMUM
# Последнее число -- minimum TTL

# Запроси несуществующее имя -- оно тоже закэшируется:
dig nonexistent.example.com
# NXDOMAIN
dig +stats nonexistent.example.com
# Второй запрос -- мгновенный, из negative cache

Negative caching экономит много трафика: если кто-то делает typo в адресе сайта, не нужно гонять весь интернет, чтобы повторно сказать «нет».

Подвох: если ты только что зарегистрировал домен, а до этого кто-то его запрашивал и получил NXDOMAIN, resolver его кэшировал. Теперь, когда домен существует, эти resolver’ы какое-то время будут отдавать NXDOMAIN из кэша. Подожди или используй другие resolver’ы.


Browser DNS-кэш

Большинство современных браузеров поддерживают свой DNS-кэш, поверх OS-кэша. Это:

  • Сокращает latency. Не нужно даже до OS resolver доходить.
  • Защищает от плохих ISP resolvers. В некоторых случаях браузер делает запросы напрямую через DoH (DNS over HTTPS), минуя ISP.
  • Создаёт путаницу. При смене IP сервера ты можешь продолжать видеть старый IP до перезапуска браузера или очистки кэша.
# Chrome -- посмотри и очисти DNS-кэш:
# В адресной строке:
chrome://net-internals/#dns
# Кнопка 'Clear host cache'

# Firefox -- about:networking#dns

# Safari -- менее доступно через UI, через Develop > Empty Caches

Это иногда экзотический источник проблем: ты обновил DNS, очистил OS кэш, но браузер всё ещё работает по старому. Перезапусти браузер или очисти DNS-кэш в нём.


Stale cache — проблема и решение

Stale cache — это запись в кэше, которая «устарела» (TTL истёк), но кэш всё ещё может её отдавать. Это происходит, когда:

  1. TTL expired, и нужно повторно запросить authoritative.
  2. Authoritative-сервер не отвечает (упал, сеть проблема).
  3. Resolver вынужден либо отдать ошибку, либо отдать stale запись.

Современные resolvers (Cloudflare, Google) реализуют serve stale: если authoritative недоступен, отдают старую запись — лучше «возможно устаревшая» страница, чем «не открылась вообще». В RFC 8767 это формализовано: запись может оставаться в «serve stale» состоянии до 7 дней при недоступности authoritative.

Это хороший паттерн для resilience: даже если у тебя падает DNS-инфраструктура, мир может ещё работать. Но это значит, что записи могут жить «дольше TTL» при определённых обстоятельствах.


Cache poisoning — атака на кэш

Идея атаки: подделать DNS-ответ так, чтобы resolver закэшировал неправильное значение. Если получится, все клиенты этого resolver’а в течение TTL будут идти на IP атакующего.

Классическая атака — Kaminsky attack (2008). Атакующий шлёт огромное количество DNS-запросов от имени resolver’а на authoritative-сервер, и параллельно шлёт поддельные ответы с предсказуемыми transaction ID. Если угадает — закэширует фейковую запись.

Защита:

  1. Random transaction ID + source port. Resolver выбирает случайный 16-битный transaction ID и случайный source port. Атакующему нужно угадать оба (~32 бита энтропии).
  2. DNSSEC. Криптографическая подпись делает поддельные ответы невалидными.
  3. DoH/DoT. Шифрование канала между клиентом и resolver’ом — атакующий не видит запросы.
  4. 0x20 encoding. Resolver рандомизирует регистр букв в имени (gOOgLE.coM), authoritative-сервер должен ответить тем же регистром — это даёт дополнительную энтропию.

В современном интернете cache poisoning сложен, но не невозможен. Для критических сервисов используется DNSSEC.


Propagation — реальное время распространения

Если ты меняешь запись с TTL=60, через сколько все клиенты увидят новое значение?

Идеальный случай: 60 секунд. Худший случай: дни, недели.

Почему худший:

  1. Long initial TTL. Если до изменения TTL был 3600, клиенты с кэшем должны его доиграть. Только потом подхватят новое TTL=60.
  2. ISP resolvers ignore TTL. Многие провайдеры (особенно мобильные) кэшируют дольше TTL для экономии трафика.
  3. Stale cache. Resolvers могут отдавать старое значение, если authoritative недоступен.
  4. Application caches. Java по дефолту кэширует forever. Многие приложения не очищают DNS-кэш при долгой работе.
  5. CDN caches. Если ты используешь CDN-провайдер с anycast, синхронизация между точками может быть медленной.

Поэтому при смене IP всегда:

  1. За день-два до миграции: снизь TTL до 60. Тогда долгие кэши истекут к моменту смены.
  2. В момент смены: обновляешь A-запись. Большинство клиентов переключатся за минуты.
  3. Период миграции (1-7 дней): держи старый сервер работающим и отдающим тот же контент или редирект на новый.
  4. Long tail (недели-месяцы): некоторые клиенты могут долго ходить на старый IP. Это «нормально», нужно ли это лечить — зависит от твоего use case.
# Понизь TTL до 60 у DNS-провайдера за день до смены:
# (Это делается в admin UI Cloudflare/Route53/etc.)

# В день миграции проверяй, что весь мир видит низкий TTL:
for resolver in 8.8.8.8 1.1.1.1 9.9.9.9 208.67.222.222; do
  echo "$resolver:"
  dig @$resolver +short www.example.com A
  dig @$resolver +noall +answer www.example.com A | head -1
done
# Если у всех TTL <= 60, можно безопасно менять

Что приложение должно делать

Несколько практических правил:

1. Не кэшируй DNS сам без хорошей причины

Многие фреймворки и языки кэшируют DNS-результат на уровне приложения. Это часто проблемнее, чем полезно. Если cluster IP меняется (балансировщик, k8s service), приложение продолжает использовать старый IP.

Особенно плохо — Java. По дефолту JVM кэширует DNS forever. Это бесконечный источник production проблем. Правильно:

// В -Dnetworkaddress.cache.ttl=60 (или меньше)
// В коде:
Security.setProperty("networkaddress.cache.ttl", "60");
Security.setProperty("networkaddress.cache.negative.ttl", "10");

2. Используй имена, не IP

В конфигурации сервиса — всегда имя. IP может смениться, имя — стабильнее. Если используешь свой DNS-сервер для service discovery (Consul, etcd, k8s) — имя позволяет ему динамически отдавать актуальный IP.

3. Делай retry на DNS ошибки

DNS-резолв может фейлиться даже на работающей сети. Retry с маленьким backoff обычно достаточно.

4. Помни о long-tail

Никогда не отключай старый сервер сразу после смены DNS. Минимум — неделя «переходного периода», лучше две.


Попробуй сам

# 1. Найди записи с разными TTL у популярных доменов:
for domain in github.com google.com www.example.com facebook.com; do
  echo "=== $domain ==="
  dig +noall +answer $domain A | head -1
done
# Видишь различия в TTL -- это политика владельца

# 2. Посмотри, как TTL декрементируется в кэше:
sudo dscacheutil -flushcache  # macOS
# (или твоё ОС-специфичное)
for i in 1 2 3; do
  dig +noall +answer +stats www.github.com A | head -1
  sleep 5
done
# TTL уменьшается на 5 при каждом повторе

# 3. Negative caching демонстрация:
sudo dscacheutil -flushcache
time dig +short totally-nonexistent-xyz123.com  # cold, ~50 мс
time dig +short totally-nonexistent-xyz123.com  # warm, <1 мс

# 4. Очисти OS-кэш и сравни:
# macOS:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
# Linux systemd-resolved:
sudo systemd-resolve --flush-caches

# 5. Сравни TTL у разных resolver'ов:
for r in 8.8.8.8 1.1.1.1 9.9.9.9; do
  echo "Resolver $r:"
  dig @$r +noall +answer www.example.com A | head -1
done

# 6. Browser-cache test (если на macOS с Chrome):
# Открой chrome://net-internals/#dns
# Видишь все домены, которые сидят в кэше Chrome

# 7. Force flush и сравни время:
sudo dscacheutil -flushcache && sudo killall -HUP mDNSResponder
time curl -s -o /dev/null https://example.com  # cold include DNS
sleep 1
time curl -s -o /dev/null https://example.com  # warm

CoreDNS TTL в Kubernetes: почему pod DNS обновляется быстро
Проверка знанийKnowledge check
Junior спрашивает: 'Я работаю в команде, которая разрабатывает Java-приложение. Оно подключается к нашему API по имени api.company.com. Иногда после смены backend IP в production некоторые экземпляры приложения продолжают слать запросы на старый IP часами, хотя TTL у нас 60 секунд. Что не так?'
ОтветAnswer
Это классический Java DNS caching gotcha. Java Virtual Machine по дефолту имеет агрессивный DNS-кэш, который игнорирует TTL. Конкретно: по дефолту JVM SecurityManager имеет настройки: networkaddress.cache.ttl = -1 (forever -- отрицательный = navсегда) networkaddress.cache.negative.ttl = 10 (10 секунд для NXDOMAIN) Это значит, что когда твой код впервые делает InetAddress.getByName('api.company.com') или открывает Socket, JVM резолвит DNS и кэширует результат до конца жизни процесса. Если IP меняется -- JVM этого не узнает, пока процесс не перезапустится. Это плохо для: - Контейнеров с long-running JVM (часто месяцы аптайма). - Микросервисов с rolling updates (новый pod получает новый IP -- старые сервисы не видят). - HA с floating IP (failover не работает -- клиенты помнят старый IP). Как починить: 1. Установить java.security или через -D флаг: -Dsun.net.inetaddr.ttl=60 -Dsun.net.inetaddr.negative.ttl=10 или в коде: java.security.Security.setProperty('networkaddress.cache.ttl', '60'); java.security.Security.setProperty('networkaddress.cache.negative.ttl', '10'); 2. Используй HTTP-клиент с собственным DNS-кэшем (например, OkHttp с custom Dns), или используй connection pool, который умеет реconnect на DNS-смену. 3. Service discovery подход: вместо имени в URL -- динамический lookup через consul/eureka/k8s service на каждом запросе. 4. Для CRITICAL сервисов используй sidecar pattern: Envoy/Istio handles DNS и connection pooling за тебя, JVM просто говорит с localhost. Конкретно для твоего случая (production со сменой IP): (a) Срочное: добавь в JVM-флаги: -Dsun.net.inetaddr.ttl=60 (b) Перезапусти все JVM-инстансы. Они начнут уважать TTL. (c) Долгосрочно: рассмотри service mesh (Linkerd/Istio) или smart load balancer, чтобы IP smashes были прозрачнее для приложения. (d) Для миграций IP делай blue-green с обоими работающими >24 часа после смены DNS. Это buys time для всех stale clients. Замечание: это не только Java problem. Node.js по дефолту резолвит на каждый запрос (не кэширует, но и не использует кэш OS). Go использует OS resolver. Python -- зависит от библиотеки. Но Java самый частый источник 'why is my app stuck on old IP'-bugs. В целом: НИКОГДА не полагайся на DNS TTL как на 'guaranteed propagation time'. TTL -- это hint, что реально кэшируется и сколько -- зависит от десятка слоёв. Production-ready приложение умеет корректно reconnect при failure.

Проверьте понимание

Результат: 0 из 0
Концептуальный
Вопрос 1 из 6. Что такое TTL в DNS-записи?

Закончили урок?

Отметьте его как пройденный, чтобы отслеживать свой прогресс

Войдите чтобы оценить урок

Прогресс модуля
0 из 5