· 16 мин чтения

Zabbix 7.4: счётчик DNS-запросов растёт, а «Change per second» показывает ноль — разбираем на своих шаблонах

Типичный звонок инженера: «На DNS-сервере счётчик запросов в Zabbix честно растёт, а график qps — прямая линия по нулю. Стоило прогнать нагрузочный тест — график ожил, появились нормальные цифры». Разбираем, почему это не баг Zabbix 7.4, а следствие одной настройки типа данных элемента — и как мы теперь всегда настраиваем такие счётчики в шаблонах DNS-серверов у клиентов.

Симптом: счётчик растёт, а «скорость» — нет

Ситуация одинаковая у нескольких наших клиентов на Windows DNS Server и на BIND: элемент данных, который опрашивает накопительный счётчик запросов (в терминах Windows — «Total Query Received», в терминах BIND — совокупные opcodes.QUERY из statistics-channels), исправно тикает вверх в Latest data. А производный элемент «запросов в секунду», построенный через шаг предобработки Change per second, на графике держит ровный ноль часами, хотя сервер явно отвечает на запросы.

Стоит запустить нагрузочный генератор (у нас это обычно dnsperf или flamethrower с профилем 500–2000 qps), график немедленно «просыпается» и показывает адекватные цифры. Первая реакция — «сервер Zabbix не успевает считать под нагрузкой». На самом деле верно ровно обратное: без нагрузки Zabbix считает честно, просто результат — дробное число меньше единицы, а хранить его физически нет возможности из-за одного параметра элемента.

У наших клиентов — юрлиц на 20–50 рабочих мест — внутренний DNS почти никогда не работает под постоянной высокой нагрузкой: основная часть запросов кэшируется на уровне рабочих станций и локальных резолверов, а на сам сервер долетают единичные обращения в минуту. Именно поэтому баг проявляется не на редких экзотических инсталляциях, а ровно на типовом клиентском ландшафте — том самом, для которого мы и собираем шаблоны мониторинга.

Как Zabbix 7.4 на самом деле считает «Change per second»

Шаг предобработки Change per second — это не магия, а простая формула, которую Zabbix Server (или Zabbix Proxy, если счётчик проходит через него) применяет к каждой новой сырой величине:

(value - prev_value) / (time - prev_time)

Здесь value — только что полученное значение счётчика, prev_value — предыдущее сохранённое значение того же элемента, а time/prev_time — метки времени этих двух опросов с точностью до микросекунд. Первое значение элемента после его создания рейт не даёт — базы для сравнения ещё нет, история начинает копиться только со второго опроса.

Возьмём реальные цифры с одного из наших DNS-серверов при интервале опроса 60 секунд: в 12:00:00 счётчик показывал 128480 запросов, в 12:01:00 — 128483. Разница 3 запроса за 60 секунд даёт 0.05 запроса в секунду. Формула отработала верно — вопрос только в том, куда Zabbix запишет число 0.05.

Важная деталь конфигурации: на одном элементе допустим только один шаг «изменения» — либо Change per second, либо Simple change (абсолютная разница без деления на время), совмещать их в одной цепочке предобработки нельзя, второй шаг такого типа Zabbix просто не позволит добавить.

Ещё один нюанс, который легко упустить: расчёт скорости завязан именно на фактические метки времени опроса, а не на номинальный Update interval из настроек элемента. Если опрос из-за загрузки poller'а или сетевой задержки сдвинулся на несколько секунд, Zabbix использует реальный интервал time - prev_time, а не «плановые» 60 секунд — формула остаётся корректной, но при разборе аномальных значений на графике стоит сверяться с фактическими метками в истории, а не с номиналом из конфигурации элемента.

Numeric (unsigned) против Numeric (float): куда девается дробь

Вот и корень проблемы. Тип данных элемента (Type of information) определяет, как значение хранится в базе — а не как оно вычисляется. Если для производного элемента с Change per second оставлен тип Numeric (unsigned) (64-битное целое без знака), Zabbix Server записывает в историю только целую часть результата. Дробная часть отбрасывается полностью — не округляется, а именно усекается.

Значение 0.05 запроса в секунду при типе Numeric (unsigned) превращается в 0. Значение 0.94 — тоже в 0. А вот 1.94 превратится в 1. Отсюда и эффект: пока трафик держится в диапазоне «доли запроса в секунду», график рисует ровный ноль. Стоит нагрузочному тесту разогнать сервер до сотен-тысяч qps — целая часть становится заметной, и график наконец показывает жизнь.

Тип Numeric (float) хранит число как есть, с сохранением дробной части — диапазон значений примерно от −999999999999.9999 до 999999999999.9999, точность порядка 15–17 значащих цифр. Именно поэтому в официальной документации Zabbix по предобработке элементов данных рекомендация звучит однозначно: если к элементу применяется расчёт скорости («rate») — то есть шаг Change per second или Simple change, — тип данных должен быть Numeric (float), даже если исходные сырые значения целые.

ПараметрNumeric (unsigned)Numeric (float)
Формат хранения64-битное целое без знакачисло с плавающей точкой
Дробная часть после Change per secondусекается до целого (0.94 → 0)сохраняется полностью
Отрицательные значенияне поддерживаютсяподдерживаются
Диапазон0 … 2^64-1 (сырой счётчик)≈ -999999999999.9999 … 999999999999.9999
Когда использовать для DNS-метриксырой накопительный счётчик запросов (мастер-элемент)элемент со скоростью — qps, rps, любой «per second»

Почему при 5 qps молчит, а при 2000 qps «оживает»

Порог не в самом Zabbix, а в арифметике. Пока среднее число запросов на интервал опроса даёт результат меньше единицы в секунду, Numeric (unsigned) обрежет его в ноль. При интервале опроса 60 секунд серверу достаточно получать в среднем меньше 60 запросов в минуту (то есть <1 qps), чтобы «Change per second» стабильно писал нули — а это совершенно нормальная нагрузка для внутреннего DNS небольшого офиса на 30–50 рабочих мест в спокойные часы.

Под нагрузочным тестом типа dnsperf -s 192.168.x.x -d queryfile -c 50 -l 60 генератор легко выдаёт 500–2000 qps — усечение дробной части в этом диапазоне визуально не заметно (2000.94 и 2000 на графике не различить), поэтому кажется, что «всё заработало», хотя на самом деле баг просто перестал быть заметен, а не исчез.

Второй усугубляющий фактор — длинный интервал опроса. Если элемент опрашивается не раз в минуту, а раз в 5 минут (300 секунд), тот же трафик даёт среднюю скорость в 5 раз меньше по модулю на единицу времени сравнения — риск попасть в зону «меньше 1» кратно выше. Для метрик скорости на низкотрафиковых сервисах (внутренний DNS, вспомогательные API) мы теперь закладываем интервал опроса не длиннее 60 секунд именно из-за этой арифметики, а не только из соображений детализации графика.

Есть и обманчивое смягчение симптома — тренды. Zabbix агрегирует часовые тренды на основе истории, и если из 60 сырых точек в час половина случайно попала выше единицы (например, в рабочие часы нагрузка чуть подросла), среднее за час по трендам может показать небольшое ненулевое число, хотя большинство отдельных секундных точек истории — честные нули из-за усечения. Разбираться в причине нужно именно по сырой истории (Latest data → History, не Trends), иначе легко сделать неверный вывод, что «в среднем всё нормально».

Порядок шагов предобработки — вторая частая ошибка

Шаги предобработки в Zabbix 7.4 выполняются строго по порядку сверху вниз, и от этого порядка зависит смысл результата, а не только цифры. Разберём на Custom multiplier — шаге, который умножает значение на константу.

Перепутанный порядок не даёт ошибку конфигурации — Zabbix спокойно её сохранит, но графики будут врать в 60 раз или в тысячу раз, и заметить это на глаз сложно, если не сверяться с сырыми числами.

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

Preprocessing → Test:\nValue: 128483 Timestamp: 2026-09-09 12:01:00\nPrevious value: 128480 Previous timestamp: 2026-09-09 12:00:00\nStep 1 result (Change per second): 0.05

Переполнение счётчика (counter wrap) — что увидит Zabbix

DNS-счётчики на практике встречаются и 32-битные, и 64-битные, в зависимости от версии BIND/Windows и способа снятия метрики. 32-битный счётчик обнуляется при достижении 4294967295, после чего следующее значение снова маленькое и формально value < prev_value.

Обработка в Zabbix предсказуема: если новое значение оказалось меньше предыдущего, шаги Change per second и Simple change не пытаются считать отрицательную скорость — они отбрасывают эту пару значений и просто ждут следующего опроса, чтобы начать отсчёт заново от новой базовой точки. Это защищает от ложных провалов графика в минус при переполнении счётчика или при перезапуске службы (у DNS-сервера счётчик сбрасывается в ноль при перезапуске named или службы «DNS Server» в Windows) — вместо провала вы получаете один «пропущенный» интервал без записи в истории именно для производной метрики скорости.

На практике современные источники — JSON-статистика BIND через statistics-channels и статистика Windows DNS Server через Get-DnsServerStatistics — отдают счётчики как 64-битные накопители, поэтому реальное переполнение почти не встречается; куда чаще «провал похож на wrap» — это именно перезапуск службы DNS после обновления или сбоя. Отличать одно от другого мы предлагаем отдельным триггером на nodata() для мастер-элемента, а не пытаться угадывать это по графику скорости.

Отдельно стоит SNMP-мониторинг сетевого оборудования рядом с DNS-сервером (интерфейсные счётчики трафика): там до сих пор встречаются 32-битные Counter32 вместо 64-битных Counter64, особенно на бюджетных коммутаторах. На гигабитном порту 32-битный счётчик байт способен обернуться за несколько десятков секунд под полной загрузкой — тогда описанный выше механизм отбрасывания «отрицательных» дельт будет срабатывать регулярно, а не как исключение. Для таких интерфейсов, если оборудование поддерживает 64-битные счётчики по SNMP, мы переключаем OID именно на них — это устраняет проблему полностью, а не просто маскирует её.

Как мы разворачиваем счётчик правильно на шаблоне DNS-сервера

Практическая схема, которую мы теперь используем как эталон для шаблонов Windows DNS Server и BIND (проект собственного мониторинга описан в скилле Zabbix, боевой сервер 7.0 LTS на площадке Dimax):

  1. Мастер-элемент — сырой накопительный счётчик. Тип данных — Numeric (unsigned), без шагов «change»-предобработки. Для BIND это обычно UserParameter, для Windows — вызов PowerShell.
  2. Дочерний (dependent) элемент «скорость запросов» — берёт значение с мастер-элемента, тип данных — Numeric (float), единственный шаг предобработки — Change per second, единица измерения в поле Units — !qps (восклицательный знак отключает автоматическое переформатирование числа Zabbix'ом).
  3. Проверка цепочки через вкладку Test до сохранения элемента в продуктив.
  4. Контрольный нагрузочный прогон с фиксацией скорости на низком трафике (5–10 qps) — именно здесь тип Numeric (unsigned) на скоростном элементе выдал бы ноль, а Numeric (float) покажет 5.00–10.00 корректно.

Для BIND мастер-элемент опирается на statistics-channels:

UserParameter=bind.queries.total,curl -s http://127.0.0.1:8053/json/v1/server | jq '.opcodes.QUERY // 0'

Для Windows DNS Server мы снимаем накопительную статистику через PowerShell-обёртку (Zabbix agent 2, UserParameter в конфиге агента), а не через уже готовый PDH-счётчик \\DNS\\Total Query Received/sec — этот счётчик Windows считает сам, и это уже посчитанная скорость, а не накопитель. Применить к ней ещё раз Change per second — вторая типовая ошибка, «двойное дифференцирование»: на выходе получится почти нулевая или бессмысленная величина, потому что скорость изменения скорости — это другая физическая величина, не то, что нужно на графике qps.

UserParameter=dns.queries.total,powershell -NoProfile -Command \"(Get-DnsServerStatistics).QueryStatistics.TotalQueries\"

Точное имя свойства нужно сверять на конкретном сервере командой Get-DnsServerStatistics | Format-List * — набор полей отличается между сборками Windows Server, это оценка по практике, а не гарантированная константа во всех версиях.

Почему именно dependent item, а не второй независимый активный/пассивный элемент с тем же ключом: dependent item не создаёт дополнительного опроса источника — он получает значение из уже выполненного сбора мастер-элемента и применяет к нему свою предобработку. Для одного DNS-сервера разница неважна, но когда через low-level discovery разворачивается шаблон сразу на десяток DNS-серверов клиента, лишний параллельный опрос того же UserParameter удваивает нагрузку на сборщик без всякой пользы — а через LLD-прототипы элементов (item prototype) схема «мастер + dependent» разворачивается на всё обнаруженное множество серверов автоматически, с уже правильными типами данных в прототипе.

Триггеры, единицы измерения и хранение — что меняется вместе с типом данных

Смена типа данных на float тянет за собой несколько практических следствий, которые стоит учесть сразу, а не по факту жалоб.

ОбластьОсобенность при Numeric (float) у скоростного элемента
Хранение историизначения пишутся в отдельную таблицу float-истории; при смене типа у уже существующего элемента старые числа не переезжают автоматически — они остаются в прежней таблице по своему типу
Трендыагрегаты avg/min/max считаются по часовым интервалам как обычно, но без потери дробной части — важно для трендов на низком трафике
Единицы измеренияполе Units со значением !qps — восклицательный знак фиксирует подпись как есть, без автоматического «K/M/G»-переформатирования Zabbix'ом
Триггерыдля алертинга по низкому трафику лучше использовать avg(/Host/dns.queries.rate,15m), а не last() — единичный дробный всплеск/провал на «рваном» qps-графике не должен сам по себе стрелять алертом

Отдельная рекомендация для тестовых окон: если планируется контрольный нагрузочный прогон и нужна более частая детализация именно на это время, временно добавляем гибкий интервал (custom interval) с коротким периодом опроса — например, 10 секунд на 30–60 минут — через настройку Update interval → Custom intervals у мастер-элемента, а затем возвращаем стандартный интервал, чтобы не перегружать сервер Zabbix историей на постоянной основе.

И последняя тонкость — обработка ошибок самого шага предобработки. У Change per second есть блок Custom on fail: по умолчанию при ошибке шага (например, первое значение элемента, для которого ещё нет предыдущей точки) значение просто не записывается в историю — это ожидаемое и безопасное поведение. Менять его на «Set value to» ради того, чтобы «график не был пустым» первые минуты после создания элемента, мы не рекомендуем: подстановка искусственного нуля в эти точки визуально неотличима от настоящего нуля из-за усечения дробной части — то есть можно случайно воспроизвести тот же самый баг вручную, только уже осознанно.

Чек-лист, который мы теперь ставим в каждый шаблон DNS-сервера

После разбора этой ситуации у нескольких клиентов подряд мы закрепили короткий список проверок при добавлении любого счётчика со скоростью — не только для DNS, но и для похожих метрик (Apache, backup, сетевые интерфейсы):

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

Можно ли просто поменять тип данных у существующего элемента с unsigned на float, не потеряв старую историю?
Технически смена типа данных в форме элемента разрешена, но история в базе Zabbix физически хранится в разных таблицах по типу значения. Уже накопленные записи в таблице для unsigned-истории не переезжают автоматически в float-таблицу — они остаются на своих местах и продолжают отображаться за прошлые периоды, а новые значения после смены типа пишутся уже в float. Для скоростных метрик мы обычно создаём новый dependent-элемент с правильным типом сразу, а старый оставляем донашивать retention и потом отключаем.
Можно ли в одной цепочке предобработки использовать и Change per second, и Custom multiplier?
Да, это стандартная и рабочая комбинация. Ограничение касается только самих операций изменения — Change per second и Simple change взаимно исключают друг друга на одном элементе, на весь остальной стек шагов (Custom multiplier, JSONPath, regex и т.д.) это ограничение не распространяется. Важен только порядок шагов относительно друг друга.
Счётчик DNS иногда резко падает — это переполнение или перезапуск службы?
Для Zabbix оба случая выглядят одинаково: новое значение меньше предыдущего, поэтому шаг Change per second отбрасывает эту пару и ждёт следующий опрос, не показывая ложный отрицательный всплеск. Различить причину по одному графику скорости нельзя — для этого мы ставим отдельный триггер nodata() или мониторим момент старта службы DNS независимо от значения самого счётчика.
Нужно ли ставить Numeric (float) на все счётчики без исключения?
Нет. Правило простое: если к элементу применяется расчёт скорости (Change per second или Simple change) или значение потенциально может стать дробным или отрицательным — используйте Numeric (float). Если элемент хранит именно сырой монотонно растущий накопитель без предобработки изменения — Numeric (unsigned) для него корректен и экономичнее по хранению.
Как быстро проверить корректность расчёта скорости до раскатки шаблона на весь парк DNS-серверов?
На вкладке Test в форме предобработки элемента можно ввести пару тестовых значений и временных меток вручную и увидеть результат каждого шага цепочки без ожидания реального опроса. Мы прогоняем через неё сценарий с низким трафиком (дельта меньше 60 единиц за минуту) — именно он вскрывает усечение до нуля при неверном типе данных, тогда как тест с большими числами эту ошибку маскирует.
📄
Скачайте подробный разбор в PDF Кейсы, статистика, типовые ошибки и чек-лист самопроверки — 12 страниц
Скачать PDF

Подпишитесь на разборы ITfresh

Раз в неделю — практичные материалы по ИТ для бизнеса: без спама, только польза.

Письмо придёт в течение минутыНе нашли его во «Входящих» — загляните в папку «Спам» или «Промоакции» и нажмите «Не спам». Так все следующие выпуски будут приходить прямо в основную почту.