Чем плох bind9
До поры до времени я как и многие полагал что bind это хороший DNS сервер и искать ему замену смысла нет, к тому же во FreeBSD он в base system.
Но выяснилось, что под большой нагрузкой в качестве кэширующего
рекурсора bind9 работает просто отвратительно.
Раз в cleaning-interval минут он чистит свой кеш от записей с истекшим TTL. Процедура это на редкость ресурсоемка - на кэше размером 256 Mb она занимает около минуты времени CPU (реального времени получается больше). И в течение этого времени он не отвечает на запросы. Оценить количество потерянных запросов можно, например, по счетчику "dropped due to full socket buffers" в
netstat -s -p udp.
Почитал архивы bind-users@ - оказалось, что проблема эта известная. Рекомендации по решению этой проблемы были такие:
- Собрать bind с ISC_MEM_USE_INTERNAL_MALLOC=1
- Уменьшить DNS_CACHE_CLEANERINCREMENT в lib/dns/cache.c
1-й пункт это использование встроенного аллокатора bind вместо
системного malloc(). Попробовал. Ничего не изменилось. Очистка кэша
осталось такой же неоправданно ресурсоемкой операцией как и с системным malloc().
2.-й пункт. Сам процесс очистки записей разбит на итерации. Т.
е. проверяются не все записи подряд, а по DNS_CACHE_CLEANERINCREMENT штук, по умолчанию это 1000. В перерывах между отдельными итерациями он что то еще делает, возможно даже отвечает на часть запросов, но в целом они идут практически одна за другой. Пробовал уменьшать это значение до 200. Потерянных запросов стало меньше, но все равно больше половины.
Увеличил в добавок к этому net.inet.udp.recvspace - количество потерь еще немного уменьшилось - во ремя чистки кеша теряется порядка половины запросов.
Тестирование
В связи с этим решил протестировать другие сервера:
PowerDNS Recursor и
MaraDNS
Тест проводился так было сделано 3 файла по 65024 ip в каждом (все ip разные). И потом эти три файла одновременно ресолвились утилитой
ip2host и наблюдалась сколько ресурсов кушает dns-сервер.
ip2host < ip_list_1 > /dev/null&
ip2host < ip_list_2 > /dev/null&
ip2host < ip_list_3 > /dev/null&
И потом второй проход для получения того же, но уже из кеша.
Результаты получились такие:
Сервер
cpu time (секунд) при получении записей которых нет в кэше
cpu time (секунд) при получении записей из кэша
Занимаемая память (RES), Mb
total
user
system
total
user
system
bind9.3.2
61
48.01
12.99
18.81
14.01
4.8
118
PowerDNS recursor 3.1.4
88.01
70.86
17.15
33.35
27.45
5.9
46
MaraDNS 1.2.12.04
103.01
87.05
15.96
60.22
54.86
5.36
95
Т. е. при нормальной работе лучшем оказался bind, немного похуже
pdns recursor и совсем плохо MaraDNS, что вполне логично если учитывать модели их работы.
Модель обработки соединений
- bind - использует FSM (конечный автомат). Для все запросов к
внешним серверам используется один и тот же сокет. Что позволяет в минимальном варианте держать постоянно открытыми всего два сокета - на 53-м порту, чтобы принимать запросы от клиентов и второй сокет для запросов к внешним серверам. Для их опроса используется select() который при всего двух дескрипторах работает эффективно (если их много select неэффективен).
- pdns recursor - тоже использует FSM, но на каждый исходящий запрос
открывает новый сокет. Это делает его более устойчивым к отравлению кэша, но требует немного больше ресурсов. Для опроса сокетов под FreeBSD используется kqueue()
- MaraDNS - использует тредовую модель (!). Сколько одновременных выполняемых запросов столько и потоков. Вполне логично, что это сопряжено с большими накладными расходами.
Очистка кеша
- bind - описано выше. т. е. очень плохо, что подтверждается практикой.
- pdns recursor - раз в 10 минут проходится 10000 записей. Проверить в синтетических тестах это сложно, но по все видимости процесс очистки кеша не должен вызывать проблем т. к. за раз проверяется не весь кеш а только 10 тысяч (т. е. сравнительно небольшая часть кеша)
- MaraDNS - на практике так же не тестировалось, но модель хорошая - никаких периодических чисток нет. Вместо этого когда не хватает места для новой записи просто удаляется несколько старых записей. Если они отсортированы по "time to die" то это требует очень мало ресурсов. Модель очень хорошая (так например делает memcached), но реализована, насколько я понял не очень хорошо.
Поддержка стандартов
- bind - кроме того, что это самый распространенный DNS сервер это еще и reference implementation, т. е. если где то стандарт можно толковать по разному, то делать надо так как это сделано в bind. Поддерживает кучу разных нужных и не очень расширений протокола DNS (в папке doc лежит 96 RFC).
- pdn recursor - серьезных проблем не замечено. Есть настройки которые делают его поведение не полностью стандартным, но это вполне осмыслено.
- MaraDNS - похоже у автора излишне творческое отношение к стандартам. В частности если прописано несколько одинаковых записей он (dns roud robin) он отдает не все, а только часть из них (из за ограничений структур данных).
Что хочется
А хочется кэширующий DNS сервер который использует модель обработки
соединений как в bind и организацию кэша подобно тому как это сделано
в
memcached: при запросе если запись есть в кэше проверять её TTL -
если не истек отдавать клиенту и обновлять метку last used, если истек то удалять и заново спрашивать у удаленных серверов. Если в кеше не хватает места удалять по LRU - т. е. удалять те записи, которые давно никто не спрашивал.
P.S.
djbdns пока не смотрел, но зная другие творения доктора Бернштейна, ничего хорошего ждать не приходится.