Сколько DNS-запросов в секунду держит ваш резолвер: методика нагрузочного замера
Когда свой резолвер начинает подтормаживать под нагрузкой — на кластере, за NAT-шлюзом или как upstream для сотен клиентов — первый вопрос всегда один: а сколько запросов в секунду он вообще способен переварить? Готового ответа нет, потому что «QPS резолвера» — это не паспортная характеристика демона, а результат замера на конкретном железе, конкретном трафике и конкретной доле попаданий в кэш. Один и тот же unbound на одном сервере держит десятки тысяч запросов в секунду, а на соседнем — захлёбывается на паре тысяч, и разница объясняется не версией софта, а тем, что именно вы у него спрашиваете.
Содержание
- Почему цифра «N запросов в секунду» без замера бесполезна
- Кэш-хит и рекурсивный запрос — два разных потолка
- UDP против TCP: разная цена одного и того же запроса
- Роль кэша: hit-rate решает больше, чем железо
- Методика замера: dnsperf и queryperf на своём сервере
- Что происходит при превышении потолка: таймауты и retry-шторм
Почему цифра «N запросов в секунду» без замера бесполезна
В интернете легко найти утверждения вида «unbound держит 50000 QPS» или «bind справляется с 20000 запросов в секунду». Эти цифры не врут — они просто получены на чужом железе, с чужим профилем запросов и чужой долей кэш-хитов, и переносить их на свой сервер бессмысленно ровно так же, как переносить чужие цифры «nginx держит N соединений» на свою конфигурацию — это разбирали отдельно на примере веб-сервера, и логика с DNS-резолвером идентична.
Потолок QPS резолвера складывается из нескольких независимых факторов, и каждый из них может стать узким местом раньше остальных:
- Тип запроса — попадание в кэш стоит на порядки дешевле, чем рекурсивный поход к вышестоящим или авторитативным серверам.
- Транспорт — UDP-запрос обрабатывается за один обмен пакетами, TCP требует установки соединения и держит состояние дольше.
- Размер и настройка кэша — маленький кэш означает частые вытеснения записей и, как следствие, больше рекурсивных запросов там, где могли быть хиты.
- Число потоков/воркеров и CPU — резолвер, упёршийся в одно ядро при многоядерном сервере, недобирает QPS не потому, что демон плохой, а потому что не сконфигурирован под железо.
- Сетевой стек и лимиты ОС — размеры сокет-буферов,
so-reuseport, конкуренция за один UDP-сокет между потоками.
Разобраться, какой из этих факторов ограничивает именно ваш сервер, можно только целенаправленным нагрузочным тестом — с реальными или близкими к реальным запросами, при постепенном увеличении интенсивности до появления ошибок.
Кэш-хит и рекурсивный запрос — два разных потолка
Главная методическая ошибка при замере DNS-производительности — гонять тест одним и тем же доменом или коротким списком популярных имён. Резолвер моментально прогревает кэш, и вы измеряете не производительность DNS-сервера, а скорость отдачи записи из памяти — операция, которая упирается в CPU и почти не зависит от сети. Цифра получится красивая и почти бесполезная, потому что реальный трафик так не выглядит.
На кэш-хите резолвер обычно тратит доли миллисекунды: разбор входящего пакета, поиск записи в кэше (у unbound это msg-cache и rrset-cache), сборка ответа, отправка. Никаких сетевых обменов наружу. Именно поэтому синтетический тест на горячем кэше показывает величины на порядок выше, чем тот же резолвер выдержит в проде с реальной долей уникальных доменов.
Рекурсивный запрос — принципиально другая история. Резолвер должен:
- определить, к какому серверу идти (или взять готовую цепочку делегирования из кэша NS-записей);
- отправить запрос и дождаться ответа, с таймаутом и возможным повтором при потере пакета;
- при отсутствии в кэше промежуточных зон — пройти несколько шагов делегирования;
- при включённом DNSSEC — дополнительно получить и проверить RRSIG/DNSKEY записи, что означает ещё несколько сетевых обменов.
Каждый такой запрос занимает не микросекунды, а миллисекунды — всё это время резолвер держит состояние запроса в памяти, ждёт ответа от внешнего сервера, которым не управляет. Именно рекурсивные запросы, а не кэш-хиты, обычно определяют реальный потолок QPS в проде: не тем, сколько операций в секунду способен выполнить CPU, а тем, сколько параллельных «ожиданий ответа» резолвер способен держать одновременно, не превышая лимиты outgoing-range у unbound или аналогичные параметры пула у bind.
Практический вывод: для честного замера нужен список запросов, где доля уникальных доменов и доля повторов примерно соответствуют вашему реальному трафику. Если вы обслуживаете корпоративную сеть с активным браузингом — кэш-хиты будут превалировать. Если резолвер сидит перед парком серверов, которые бьют DNS-запросами к внешним API и постоянно меняющимся именам — доля рекурсии окажется куда выше, и потолок будет соответственно ниже.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверUDP против TCP: разная цена одного и того же запроса
DNS исторически живёт на UDP порт 53 — один запрос укладывается в один пакет туда и один обратно, без установки соединения. Это дёшево: резолвер не хранит состояние соединения между запросом и ответом дольше, чем нужно для сборки ответа. Природа этой разницы в цене UDP и TCP касается не только DNS, но для резолвера она особенно заметна, потому что типичный запрос — это одна короткая транзакция, а не поток данных.
TCP включается в DNS в нескольких случаях:
- ответ не помещается в UDP-пакет и приходит с флагом truncated — это отдельная и частая причина непонятных обрывов резолвинга, особенно при DNSSEC, где подписи заметно раздувают размер ответа;
- зонный трансфер (AXFR/IXFR) между серверами всегда идёт по TCP;
- клиент или резолвер явно форсирует TCP, например через DNS-over-TCP как обходной путь при проблемах с фрагментацией UDP.
С точки зрения нагрузки TCP-соединение — это накладные расходы, которых нет у UDP: установка соединения, файловый дескриптор на весь срок его жизни, буферы на приём и отправку, закрытие по окончании. Резолвер со статически ограниченным числом одновременных TCP-клиентов (у bind это tcp-clients, у unbound — stream-wait-size как ограничитель по памяти на очередь TCP) может упереться именно в этот лимит при аномальном росте доли TCP — например, если у вас массово включили DNSSEC-валидацию, а до этого его не было.
Практический смысл для замера: тестируйте отдельно чистый UDP-профиль и профиль со значимой долей TCP (доля обычно в пределах единиц процентов для типичного трафика — но это ориентир, не норматив, у вас может быть иначе в зависимости от DNSSEC и MTU в сети). Если резолвер держит N запросов в секунду на чистом UDP и заметно меньше на смеси с TCP — это не аномалия, а ожидаемое поведение.
Роль кэша: hit-rate решает больше, чем железо
Кэш-хитрейт — это доля запросов, на которые резолвер ответил из памяти, не обращаясь наружу. Именно эта метрика, а не абсолютный QPS, лучше всего объясняет, почему один и тот же резолвер в одних условиях летает, а в других — задыхается при вдвое меньшей нагрузке.
У unbound статистику по кэшу можно снять прямо из консоли управления:
unbound-control stats_noreset | grep -E "cache|prefetch|recursion"
Ключевые метрики: total.num.cachehits против total.num.cachemiss, а также total.recursion.time.avg — среднее время обработки запроса, потребовавшего рекурсии. Если доля хитов низкая при заведомо повторяющемся трафике (например, сеть офиса, где сотни клиентов ходят на одни и те же популярные домены) — это почти всегда означает, что кэш либо слишком мал, либо TTL записей в нём искусственно занижен апстримом, и записи вытесняются или устаревают быстрее, чем успевают повторно запрашиваться. Разбор того, кто вообще формирует ответ, который вы получаете из кэша резолвера, полезен, если непонятно, откуда берутся расхождения между тем, что отдаёт ваш кэш, и тем, что реально лежит у авторитативного сервера.
Увеличение кэша — не бесплатная операция, но и не дорогая относительно объёма оперативной памяти современного сервера:
msg-cache-size: 256m
rrset-cache-size: 512m
cache-max-ttl: 86400
prefetch: yes
prefetch-key: yes
prefetch: yes обновляет часто запрашиваемую запись заранее, до истечения TTL, так что клиент не попадает на холодный рекурсивный запрос в момент истечения — ценой небольшой фоновой нагрузки на сеть. Ориентировочно на сервере с несколькими гигабайтами свободной памяти такие значения кэша не создают проблем, но конкретный выигрыш в хитрейте сильно зависит от вашего трафика — если доменов исходно мало повторяющихся (телеметрия, короткоживущие поддомены CDN), рост кэша даст меньше, чем в чужих кейсах. Учтите и общий объём памяти сервера: если он выполняет ещё какие-то роли, конкуренция за память может обнулить эффект от увеличенного rrset-cache-size.
Методика замера: dnsperf и queryperf на своём сервере
Замерять QPS «на глаз», глядя на CPU в top во время реального трафика, — плохая идея: реальный трафик не управляем и не воспроизводим, вы не узнаете момент отказа, пока клиенты не начнут жаловаться. Правильный подход — управляемый нагрузочный тест с постепенным наращиванием интенсивности до появления ошибок или деградации задержки.
Инструмент dnsperf (из пакета DNS-OARC dnsperf/resperf) — стандартный выбор для такого теста. Он отправляет запросы из подготовленного списка с заданной интенсивностью и печатает статистику по завершении:
dnsperf -s 192.0.2.10 -p 53 -d queries.txt -c 20 -l 60 -Q 5000
Где -s — адрес тестируемого резолвера, -d — файл со списком запросов (домен и тип записи в каждой строке), -c — число параллельных клиентов, -l — длительность в секундах, -Q — целевая интенсивность запросов в секунду. Ключевая часть — файл queries.txt: если он содержит десяток доменов по кругу, вы измерите кэш-хиты; если реалистичную смесь популярных и уникальных имён — получите более честную картину.
example1.com A
example2.net AAAA
sub.example3.org A
random-uuid-here.example4.io A
Для поиска именно точки отказа (а не проверки одного значения QPS) удобнее resperf из того же набора — он сам постепенно повышает интенсивность запросов и находит границу, после которой начинает расти доля потерянных ответов и задержка:
resperf -s 192.0.2.10 -d queries.txt -m 500 -M 30000
Более старый инструмент queryperf из состава BIND тоже применим для базового замера, но он однопоточный и сам может стать узким местом раньше тестируемого резолвера на высоких интенсивностях — для по-настоящему высоких QPS dnsperf и resperf надёжнее, потому что многопоточны на стороне генератора нагрузки.
Что смотреть в выводе теста:
- Queries completed vs. Queries lost — доля потерянных ответов (по таймауту со стороны клиента теста) начинает расти задолго до полного отказа резолвера — это первый сигнал приближения к потолку;
- Average Latency и особенно перцентили — резкий рост задержки на фоне ещё приемлемого процента потерь означает, что резолвер уже работает на пределе, просто пока не роняет запросы;
- Queries per second фактические против заданных
-Q— если фактический QPS стабильно ниже заданного, резолвер (или сама тестовая машина) не успевает обрабатывать поток на этой интенсивности.
Важный методический момент: генератор нагрузки и тестируемый резолвер не должны конкурировать за одни и те же ресурсы. Запускайте dnsperf с отдельного сервера, а не локально на машине с резолвером — иначе вы измеряете суммарную нагрузку на CPU от обоих процессов, а не реальный потолок резолвера.
Что происходит при превышении потолка: таймауты и retry-шторм
Когда резолвер упирается в реальный потолок, деградация обычно не выглядит как аккуратный отказ с понятной ошибкой — она проявляется как нарастающая цепь вторичных эффектов, которую тяжелее диагностировать постфактум, чем сам первичный затор.
Первым делом растёт задержка ответа — резолвер ещё отвечает на все запросы, но медленнее. Клиентские резолверы (например, glibc с настройками timeout и attempts в resolv.conf) обычно ждут ответ ограниченное время — типично одну-две секунды — и при его отсутствии повторяют запрос. Здесь и начинается retry-шторм: клиент, не дождавшийся ответа, не «отступает», а шлёт повторный запрос — тот же резолвер получает вторую копию поверх ещё не обработанной первой. Если перегрузка массовая (сотни серверов одновременно выходят на плато нагрузки), эффект накапливается: доля повторов в общем потоке растёт, полезная работа резолвера падает, а видимая нагрузка растёт ещё сильнее от собственных же клиентов. Это классический thundering herd применительно к DNS, способный удерживать резолвер в перегруженном состоянии даже после того, как исходный всплеск легитимного трафика уже прошёл.
Для рекурсивных резолверов есть второй слой той же проблемы — пул одновременных запросов наружу. У unbound это регулируется параметрами outgoing-range (диапазон исходящих портов, ограничивающий число параллельных запросов к апстриму) и num-queries-per-thread. При исчерпании пула новые рекурсивные запросы встают в очередь или сбрасываются — резолвер может начать отвечать SERVFAIL на превышающие лимит запросы либо копить очередь, наращивая задержку для всех.
num-threads: 4
num-queries-per-thread: 4096
outgoing-range: 8192
so-reuseport: yes
Увеличение num-queries-per-thread и outgoing-range расширяет пул одновременных запросов, но требует больше памяти и файловых дескрипторов — это не бесплатное решение, а сдвиг узкого места на другой ресурс, который тоже стоит проверить нагрузочным тестом, а не менять вслепую.
Практический вывод для эксплуатации: мониторить нужно не только сам факт ошибок (SERVFAIL, таймауты), но и тренд задержки — она растёт раньше, чем начинают появляться явные отказы, и даёт время среагировать до того, как ретрай-шторм наберёт обороты.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
dnsmasq тоже можно тестировать так же, как unbound и bind?
Да, методика та же — dnsperf не различает, какой демон слушает порт 53. Но учитывайте архитектурное отличие: dnsmasq по умолчанию однопоточный и не рассчитан на большие рекурсивные нагрузки — для домашней сети или небольшого офиса этого достаточно, для резолвера перед парком серверов обычно выбирают unbound или bind именно из-за многопоточности и более гибкого управления кэшем.
Можно ли просто взять чужой готовый список запросов для dnsperf вместо своего?
Технически да, в комплекте DNS-OARC есть примеры файлов запросов, но результат будет отражать чужой профиль трафика, а не ваш. Для осмысленного числа лучше выгрузить реальные домены из логов вашего резолвера (если логирование включено) или трафика приложений и собрать список с примерно вашей долей повторов.
Что важнее для QPS — CPU или память?
Зависит от того, где реальный потолок. Кэш-хиты — операция, ограниченная CPU и скоростью доступа к структурам в памяти. Рекурсивные запросы упираются не столько в CPU, сколько в число параллельных ожиданий ответа от внешних серверов — здесь важнее корректно выставленные лимиты пула запросов и сетевые буферы, чем голая частота процессора.
Резолвер прошёл тест на 10000 QPS — значит, столько он и выдержит в проде?
Только если тестовый профиль запросов был близок к реальному по доле кэш-хитов, доле TCP и характеру доменов. Синтетический тест на горячем кэше даст сильно завышенную цифру относительно того, что резолвер покажет на живом трафике с заметной долей уникальных рекурсивных запросов.
DNSSEC сильно снижает потолок QPS?
Заметно снижает для доли запросов, требующих валидации с нуля (не из кэша) — каждая проверка добавляет обмены за RRSIG и DNSKEY записями и вычислительную нагрузку на саму криптографическую проверку. На кэш-хитах разница почти не ощущается, поскольку валидированный результат уже лежит в кэше.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →