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 — шаге, который умножает значение на константу.
- Если нужно перевести единицы измерения самого сырого счётчика (например, источник отдаёт счётчик в тысячах запросов, а вам нужны штуки) — множитель ставится до Change per second, он масштабирует входные данные перед расчётом скорости.
- Если нужно изменить временную базу уже готовой скорости (например, перевести qps в «запросов в минуту», умножив на 60) — множитель ставится после Change per second, он работает с уже посчитанной дробной скоростью.
Перепутанный порядок не даёт ошибку конфигурации — 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):
- Мастер-элемент — сырой накопительный счётчик. Тип данных — Numeric (unsigned), без шагов «change»-предобработки. Для BIND это обычно UserParameter, для Windows — вызов PowerShell.
- Дочерний (dependent) элемент «скорость запросов» — берёт значение с мастер-элемента, тип данных — Numeric (float), единственный шаг предобработки — Change per second, единица измерения в поле Units —
!qps(восклицательный знак отключает автоматическое переформатирование числа Zabbix'ом). - Проверка цепочки через вкладку Test до сохранения элемента в продуктив.
- Контрольный нагрузочный прогон с фиксацией скорости на низком трафике (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, сетевые интерфейсы):
- На мастер-элементе с сырым накопительным значением — Numeric (unsigned), без шагов change-предобработки.
- На производном элементе со скоростью — обязательно Numeric (float) и ровно один шаг Change per second или Simple change.
- Порядок Custom multiplier относительно Change per second выбран осознанно: до — для масштабирования входа, после — для смены временной базы результата.
- Цепочка предобработки прогнана через вкладку Test с реальными парами «значение/время» до публикации на шаблон.
- Единица измерения зафиксирована как
!qps(или аналог) для читаемости графиков и триггеров. - Проверка на низком трафике (не только на нагрузочном тесте) — именно она вскрывает баг с усечением до нуля.
- Отдельный триггер
nodata()на мастер-элементе — чтобы отличать «сервис перезапустился» от «переполнение счётчика», а не гадать по форме графика скорости.
Частые вопросы
- Можно ли просто поменять тип данных у существующего элемента с 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 единиц за минуту) — именно он вскрывает усечение до нуля при неверном типе данных, тогда как тест с большими числами эту ошибку маскирует.