MAATRIX / Блог / Предел числа зон и записей в своём DNS: когда перезагрузка зон становится минутной

Предел числа зон и записей в своём DNS: когда перезагрузка зон становится минутной

MAATRIX

Свой DNS-сервер на BIND или PowerDNS обычно ставят один раз и годами не трогают конфигурацию — работает и работает. Но если через него проходит десяток тысяч зон или API постоянно добавляет и меняет записи (провижининг доменов, DDNS для клиентов, автоматика CI/CD), в какой-то момент rndc reload начинает выполняться не долю секунды, а секунды или десятки секунд, память процесса named неожиданно растёт быстрее числа зон, а запись новой записи через API упирается не в DNS-сервер, а в то, что стоит у него за спиной. Разберём, где именно проходят эти три разных предела и что делать до и после того, как в них упрётесь.

Три разных предела: reload, память, запись

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

Предел перезагрузки конфигурации. Классический BIND при rndc reconfig или полном rndc reload (без указания конкретной зоны) обходит все зоны, объявленные в named.conf, и для изменившихся — перечитывает файл и перестраивает внутреннюю структуру данных зоны. С ростом числа зон растёт и время такого обхода: не потому, что каждая зона медленнее обрабатывается, а потому, что их просто больше. При тысячах зон полный reload способен занимать заметно дольше секунды, и это ощущается именно в момент правки named.conf (добавили зону, поменяли ACL) — а не при обычных обновлениях данных внутри уже существующих зон.

Предел памяти. BIND хранит содержимое каждой загруженной зоны полностью в оперативной памяти в виде red-black дерева, независимо от того, сколько раз к ней обращаются. Это касается и primary, и secondary — вторичная зона, полученная по AXFR/IXFR, живёт в памяти точно так же целиком. Рост здесь линейный по суммарному числу записей во всех зонах, а не по числу зон — тысяча зон по десять записей и одна зона в десять тысяч записей нагружают память примерно одинаково.

Предел производительности записи. Проявляется только при активных динамических изменениях: DDNS через nsupdate, автоматическое провижининг доменов через API, ACME-валидация DNS-01 у большого числа клиентов. Каждое такое изменение — транзакция: обновление данных в памяти, запись в журнал (.jnl-файл у BIND), инкремент serial зоны и часто рассылка NOTIFY вторичным серверам. При высокой частоте записи в одну зону узким местом становится не хранение, а последовательная обработка этих транзакций.

Три предела важно держать в голове отдельно: типичная реакция «добавить памяти» решает второй, но не первый и не третий, а «включить IXFR» решает первый и отчасти третий, но не помогает с памятью.

Как измерить: методика на своих данных

Прежде чем что-то оптимизировать, посчитайте, где вы сейчас находитесь — цифры сильно зависят от железа, версии ПО и структуры зон, мерить стоит на своих данных, а не ориентироваться на чужие числа.

Число зон и суммарное число записей в BIND:

# число зон, объявленных в конфигурации
rndc status | grep -i "number of zones"

# суммарный размер зон на диске как грубая оценка числа записей
find /etc/bind/zones -name '*.zone' -exec wc -l {} + | tail -1

Время полной перезагрузки конфигурации:

time rndc reconfig
# или, если нужно перечитать все изменившиеся зоны:
time rndc reload

rndc reload без аргумента обходит весь список зон и для каждой сверяет, изменился ли файл на диске — даже если фактически ничего не поменялось, сама сверка тысяч зон занимает время. Перезагрузка одной конкретной зоны — операция другого порядка:

time rndc reload example.com

Память процесса и её рост при добавлении зон:

# резидентная память процесса named
ps -o pid,rss,vsz,cmd -C named

# более подробная разбивка по сегментам памяти
pmap -x $(pgrep named) | tail -5

Полезно снять эту метрику до и после добавления заметной пачки тестовых зон с известным числом записей — так вы получите свой ориентир «мегабайт на тысячу записей» вместо абстрактной цифры из документации. Общую методику оценки памяти под BIND разбирали в статье сколько RAM нужно для BIND9.

Скорость применения одной динамической записи:

time nsupdate <<EOF
server 127.0.0.1
zone example.com
update add test-$RANDOM.example.com 300 A 203.0.113.10
send
EOF

Если прогнать это в цикле по сотне записей подряд и время на запись растёт, а не остаётся стабильным, — вы уже упираетесь в предел записи, а не измеряете сетевую задержку.

Для PowerDNS аналогичные метрики снимаются через встроенную статистику:

pdns_control show uptime
pdns_control show qsize-q
pdnsutil list-all-zones | wc -l

и через встроенный REST API (/api/v1/servers/localhost/statistics) — он даёт время выполнения запросов и число активных бэкенд-соединений, что для PowerDNS с SQL-бэкендом информативнее метрик самого процесса.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Полная перезагрузка против инкрементальных обновлений

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

Динамические обновления не требуют reload. Если запись меняется через nsupdate или API поверх RFC 2136, BIND обновляет зону прямо в памяти и дописывает изменение в журнал (.jnl) — файл зоны на диске при этом не трогается, пока не случится rndc sync или плановая синхронизация. Это принципиально дешевле полного rndc reload, и если автоматика меняет записи через прямое редактирование файла зоны с последующим rndc reload <zone> — это стоит переделать на динамические обновления через nsupdate или API PowerDNS: экономия на порядок при частых изменениях.

IXFR вместо AXFR между primary и secondary. Когда зона меняется, вторичные серверы должны получить обновление. По умолчанию это может происходить через полный перенос зоны (AXFR) — secondary скачивает зону целиком заново при каждом изменении serial, и при большой зоне с частыми изменениями это ощутимая нагрузка на сеть и CPU обеих сторон. Инкрементальный перенос (IXFR) передаёт только разницу между старым и новым serial. Включается на primary через журналирование различий:

zone "example.com" {
    type primary;
    file "/etc/bind/zones/db.example.com";
    allow-transfer { key transfer-key; };
    also-notify { 192.0.2.10; };
    ixfr-from-differences yes;
};

ixfr-from-differences yes заставляет BIND формировать журнал различий даже при изменениях, внесённых прямым редактированием файла зоны, — стоит включать глобально в options, если вторичных серверов несколько и зоны заметного размера.

Есть и обратная сторона. При очень высокой частоте изменений в одной зоне (десятки обновлений в секунду) генерация NOTIFY и IXFR-диффов на каждое изменение и рассылка их всем вторичным серверам сама становится нагрузкой — эффект веерной рассылки (fanout), когда N secondary одновременно запрашивают IXFR после одного NOTIFY. Для таких сценариев в BIND есть notify-delay — короткое окно, в течение которого несколько подряд идущих обновлений объединяются в один NOTIFY, а не рассылаются на каждое отдельно.

У PowerDNS механика другая, но идея та же. С бэкендом bind PowerDNS ведёт себя похоже на классический BIND. С SQL-бэкендом (gmysql, gpgsql) понятия «reload зоны» в привычном смысле почти нет — правки идут прямым запросом в базу, а PowerDNS подхватывает изменения через отслеживание serial при периодическом опросе. Инкрементальные обновления в сторону secondary PowerDNS поддерживает так же через IXFR, если бэкенд умеет отдавать журнал изменений.

Память: когда зоны перестают помещаться

Раз каждая зона в BIND хранится в памяти целиком, суммарная память процесса растёт вместе с общим числом записей во всех обслуживаемых зонах — и это единственный из трёх пределов, который решается «в лоб» добавлением RAM, но у него есть практический потолок, за которым добавлять память дальше становится дороже, чем сменить архитектуру хранения.

Практический способ спрогнозировать свой предел — не искать универсальную цифру «байт на запись» (она зависит от типа записей, длины имён, версии BIND и структуры зоны), а построить свою зависимость: снять память процесса на текущем числе записей, добавить контролируемую пачку тестовых зон известного размера, снять память повторно, посчитать разницу и экстраполировать на плановый рост.

Отдельно стоит учитывать, что secondary-серверы несут ту же нагрузку по памяти, что и primary — каждый должен вмещать полный набор зон, а не долю. Горизонтальное масштабирование размажет нагрузку по запросам между серверами, но не снизит требования к памяти на каждом отдельном узле — это ограничение архитектуры «полностью in-memory».

PowerDNS с SQL-бэкендом устроен иначе: данные зон живут в базе, а не полностью в адресном пространстве процесса pdns_server. Процесс держит в памяти только кэш «горячих» ответов и метаданные, а не весь набор записей всех зон — нагрузка никуда не исчезает, а перекладывается на базу данных со своими требованиями к памяти под буферный кэш. Сравнение подходов BIND и PowerDNS по архитектуре разбирали в статье BIND9 или PowerDNS: что выгоднее и когда.

Производительность записи: где упирается динамический DNS

Если у вас статичная инфраструктура и записи меняются от случая к случаю, эта ось предела вас скорее всего не коснётся. Но при DDNS для клиентов, автоматическом провижининге поддоменов или частой ACME DNS-01 валидации записи через API картина другая: узкое место обычно не в самом DNS-протоколе, а в том, что стоит за ним.

Для BIND каждая динамическая транзакция — это блокировка зоны на время применения изменения, дозапись в журнал (с fsync, если не отключён явно) и, при включённых secondary, генерация уведомления. При последовательных обновлениях одной зоны с высокой частотой эти операции сериализуются — прирост частоты записи упирается в скорость диска под журналом и в однопоточную обработку транзакций одной зоны.

Для PowerDNS с SQL-бэкендом узкое место чаще всего — сама база: каждое изменение записи — это UPDATE/INSERT в таблицу records, и вы упираетесь в то же самое, во что упирается любое приложение с интенсивной записью в MySQL или PostgreSQL — блокировки строк/таблиц, скорость fsync при commit, размер пула соединений. Если параллельно растёт и число самих зон (строк в таблице domains), это пересекается с ограничениями на число объектов в схеме — тему отдельно разбирали в статье про предел числа таблиц в базе: там речь про таблицы, а не про DNS-записи, но логика деградации та же.

Практические меры: группировать пакетные изменения там, где это возможно — одна транзакция на десять записей дешевле десяти отдельных, особенно из-за накладных расходов на fsync и NOTIFY; не гонять rndc reload в цикле после каждой динамической записи, если изменения идут через nsupdate; для PowerDNS следить за индексами в таблицах бэкенда (name, domain_id в records) и за пулом соединений; ограничивать скорость записи на уровне приложения (rate limiting на API провижининга), если всплески создаются автоматикой, а не реальной нагрузкой — это дешевле, чем разгонять DNS-бэкенд под пиковую нагрузку.

Когда переходить с текстовых конфигов на базу данных

Текстовые зоны в BIND — простой и надёжный вариант, пока число зон и частота изменений остаются в пределах, которые вы способны отслеживать вручную или через собственные скрипты вокруг named.conf. Переход на хранение в базе данных (PowerDNS с gmysql/gpgsql-бэкендом, либо BIND с модулем DLZ) имеет смысл не при конкретном числе зон, а при появлении конкретных симптомов:

Добавление зоны требует правки конфигурации и reload. Пожалуй, главный практический аргумент. В классическом BIND новая зона — это новая секция в named.conf плюс rndc reconfig. Если зоны создаются автоматически через API (регистрация поддомена, подключение клиентского домена), встраивать в этот процесс правку конфигурационного файла — источник гонок: два параллельных провижининга, пишущих в один named.conf, легко создают конфликт. PowerDNS с SQL-бэкендом снимает эту проблему полностью — новая зона это INSERT в таблицу domains, reload не требуется.

Число зон само по себе создаёт время reload, неприемлемое для рабочего процесса. Если полная перезагрузка конфигурации уже занимает заметное время и мешает регулярным операциям (обновление ACL, добавление ключей TSIG), а обойтись точечным rndc reload <zone> не получается из-за характера изменений — это сигнал двигаться в сторону бэкенда, где такого понятия просто нет.

Нужна интроспекция и массовые операции поверх зон. «Показать все зоны клиента X», «найти все записи на выводимый из эксплуатации IP» — на текстовых файлах это grep по каталогу, работающий, но не масштабирующийся красиво за пределы нескольких тысяч файлов. В базе это обычный SQL-запрос с индексом.

Обратная сторона перехода: SQL-бэкенд добавляет полноценную зависимость от СУБД — её нужно бэкапить отдельно (см. бэкап и восстановление PowerDNS), мониторить её метрики, и при сбое базы DNS перестаёт отвечать на изменения (хотя обычно продолжает отдавать уже закэшированные ответы). Если у вас десяток статичных зон, которые меняются раз в месяц, переход ради самого перехода не нужен. Практическая установка PowerDNS с SQL-бэкендом разобрана в статье как установить и настроить PowerDNS.

КритерийТекстовые зоны (BIND)SQL-бэкенд (PowerDNS)
Добавление зоныПравка конфига + reloadINSERT в БД, без reload
Скорость единичного измененияБыстро при nsupdate, медленно при ручной правке файла + reloadЗависит от производительности БД
Память DNS-процессаРастёт линейно с числом записейОграничена кэшем, не всем набором данных
Массовые выборки/отчётыgrep по файлам зонSQL-запросы с индексами
Дополнительная зависимостьНетСУБД: бэкап, мониторинг, доступность
Порог целесообразностиДесятки-сотни зон, редкие измененияАвтоматическое провижининг, тысячи зон, частая запись

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Есть ли официальный жёсткий лимит на число зон в BIND?

Нет, формального ограничения не существует — предел практический, определяется памятью, временем reload и вашими операционными процессами.

IXFR решает проблему памяти?

Нет, он снижает нагрузку на сеть и CPU при передаче изменений между primary и secondary, но каждая зона по-прежнему хранится в памяти целиком на каждом сервере — на предел по памяти IXFR не влияет.

Можно ли включить IXFR только для крупных зон, а остальные оставить как есть?

Да, ixfr-from-differences задаётся как глобально в options, так и в конкретной секции zone — разумно включать выборочно там, где зоны большие и меняются часто.

PowerDNS с SQL-бэкендом снимает предел по производительности записи?

Не снимает, а переносит: вместо ограничений DNS-процесса вы упираетесь в производительность СУБД под записью — отдельная задача со своими инструментами оптимизации (индексы, пул соединений, репликация).

Стоит ли переходить на БД-бэкенд заранее, «про запас»?

Обычно нет — переход добавляет операционную сложность, и если у вас нет автоматического провижининга зон, текстовые зоны с грамотно настроенным IXFR закрывают потребность дольше, чем кажется на старте.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →