Рекурсивні DNS, що кешують, або чим поганий bind9 (bind dns cache tune speed optimization recursive)
До певного часу я як і багато хто вважав, що bind це хороший DNS сервер і шукати йому заміну сенсу немає, до того ж у FreeBSD він у base system. Але з'ясувалося, що під великим навантаженням як кешуючий рекурсор bind9 працює просто огидно. Раз в cleaning-interval хвилин він чистить свій кеш від записів з TTL. Процедура це рідкість ресурсомістка - на кеші розміром 256 Mb вона займає близько хвилини часу CPU (реального часу виходить більше). І впродовж цього часу він не відповідає на запити. Оцінити кількість втрачених запитів можна, наприклад, за лічильником «dropped due to full socket buffers» у netstat -s -p udp.
Почитав архіви bind-users@ — виявилося, що ця проблема відома. Рекомендації щодо вирішення цієї проблеми були такі:
1. Зібрати bind з ISC_MEM_USE_INTERNAL_MALLOC=1
2. Зменшити 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-сервер.
І потім другий прохід для отримання того самого, але вже з кешу.
Результати отримали такі: Сервер cpu time (секунд) при отриманні записів яких немає в кеші cpu time (секунд) при отриманні записів з кешу Займана пам'ять (RES), Mb
Т. е. при нормальній роботі кращим виявився 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 - серйозних проблем не помічено. Є настройки які роблять його поведінку не повністю стандартною, але це цілком осмислено.
А хочеться кешуючий DNS сервер який використовує модель обробки з'єднань як в bind і організацію кеша подібно до того як це зроблено в memcached: при запиті якщо запис є в кеші перевіряти її TTL — якщо не закінчився віддавати клієнту і оновлювати мітку last used, якщо закінчився видаляти і заново запитувати у віддалених серверів. Якщо в кеші бракує місця видаляти по LRU - тобто видаляти ті записи, які давно ніхто не питав.
P.S. djbdns поки не дивився, але знаючи інші твори доктора Бернштейна, нічого хорошого чекати не доводиться.
pdnsd поки не дивився, але він тредовий. А тредові програми як правило працюють гірше FSM (і мають більше проблем з надійністю). Іншесправа що на FSM не всі завдання можна покласти (без титанічних зусиль), але рекурсивний DNS добре реалізується у вигляді FSM. Що зроблено в bind і Powerdnsd. Буде час та pdnsd протестую. Але поки що кандидати production це powerdns recursor і maradns (він хоч і тредовий але з кешем за описом добре працює). Що стосується djbdnsd, то він уже 5 років не підтримується і у нього дурна ліцензія, яка не дозволяє поширювати змінений код.
Частина його недоліків описана тут
Поки кандидати в production це PowerDNS recursor і MaraDNS, але потрібне додаткове тестування, вивчення їх вихідного коду та вивчення ступеня відповідності стандартам. bind використовується практично повсюдно, і якщо щось не може проресолвувати bind це не зможе проресолвити більшість. При цьому цілком можуть існувати хости які не зможуть коректно проресолити Power і Mara, тому що вони порівняно рідко використовуються і такі проблеми можуть бути довго не поміченими. І це необов'язково можуть бути помилки в софті, це може бути і якесь поєднання налаштувань різних віддалених DNS серверів. Що стосується bind тобто ще такий негативний на мій погляд момент — доступ до CVS тільки платний, відкритого доступу до RT (bug tracker) немає і не планується. Т. е. сторонній людині підключитися до процесу розробки дуже складно.
Правильно виготовлена FSM масштабується дуже добре. Так, у випадку SMP щоб використовувати всі процесори потрібно або форкати кілька процесів (як це робить nginx) або створювати кілька потоків (як це вміє bind9, але поки робота в режимі треда там реалізована погано).
Що стосується того, що не паралеліться то паралелити потрібно тільки тоді, коли єоперації, що блокуються. Сокети вміють працювати в неблокованому режимі, дискові операції кешируючого dns не потрібні, завдань які вимагають тривалої роботи CPU при правильному дизайні теж бути не повинно.
Безумовно у FSM є свої недоліки, але в такому завданні як кешуючий dns проявляється тільки один з них (і то не повною мірою) - більш висока складність реалізації, порівняно з тредової моделлю.
У принципі можна так на пальцях пояснити, чому на такому завданні FSM масштабується краще:
«A computer is a state machine. Threads are for people who can't - Alan Cox