Zabbix 7.4 и Discard unchanged with heartbeat: почему триггер ждёт часы при минутном опросе
Вы включили throttling, чтобы история перестала съедать диск. Объём history_uint упал вдвое, все довольны. А через две недели служба записи на видеосервере детского сада падает в 03:12, и письмо приходит в 05:15 — при опросе раз в минуту. Ни агент, ни сеть, ни очередь тут ни при чём. Это ровно то, за что вы заплатили экономией места: отброшенное значение не попадает в базу, а значит, не пересчитывает триггер. Ниже — механика того, что именно ломается (функции по #num, nodata(), trends), разбор реального стенда с таймлайном аварии, правило выбора heartbeat, которым я пользуюсь, и чек-лист по уже настроенным триггерам.
Discard unchanged — это не сжатие истории, это удаление значения
Начну с формулировки, которая объясняет девять из десяти сюрпризов. Discard unchanged и Discard unchanged with heartbeat — не «хранить реже» и не «сжимать». Это удаление значения до того, как сервер вообще узнал, что оно приходило. Шаг отрабатывает в preprocessing manager: если результат совпал с предыдущим, значение исчезает. В базу не пишется, в value cache не кладётся, ни один триггер по нему не пересчитывается.
Мануал 7.4 говорит об этом прямым текстом, без оговорок: если значение отброшено, оно не сохраняется в базе данных и Zabbix server не знает, что это значение было получено; никакие триггерные выражения не будут вычислены, поэтому проблемы по связанным триггерам не будут созданы или закрыты; функции работают только по данным, которые реально сохранены в базе; а так как trends строятся по данным из базы, то если за час не сохранено ни одного значения, за этот час не будет и trends.
Второе предложение важнее первого. Триггер в Zabbix — вещь событийная. Выражение пересчитывается тогда, когда по элементу из этого выражения пришло новое сохранённое значение. Отдельно живут только функции, зависящие от времени, — их дёргает history syncer по таймеру. Причём «зависящие от времени» в терминах документации — это только nodata() и функции даты/времени (now(), time(), dayofweek() и т. п.); min(/host/key,5m) к ним не относится, окно в пять минут не делает триггер таймерным. Всё остальное подчиняется правилу «нет записи — нет пересчёта». Получается, что throttling буквально выключает триггер на всё время, пока метрика не меняется. Что для дискретной метрики вроде «служба запущена / служба остановлена» означает: выключает почти всегда.
Heartbeat-вариант придуман как раз чтобы этого избежать. Параметр задаётся в секундах, поддерживаются положительные целые с минимумом 1 секунда и временные суффиксы вида 30s, 1m, 2h, 1d. Можно подставлять пользовательские макросы и LLD-макросы — это пригодится, я к этому вернусь. Важное ограничение платформы: на один элемент данных допускается только одна опция throttling, скомбинировать «просто Discard unchanged» и heartbeat нельзя.
- Где throttling реально экономит: статусы служб `service.info[...,state]`, link status портов, состояние RAID-массивов и блоков питания, версии прошивок и ПО, текстовые элементы, результаты `vfs.file.contents`, метаданные из LLD.
- Где экономия близка к нулю: `system.cpu.util`, счётчики трафика, `vm.memory.size`, латентность — любые float-метрики. Они меняются почти на каждом опросе, throttling там только добавляет работы preprocessing manager.
- Где throttling опасен по умолчанию: `agent.ping`, внутренние `zabbix[...]`-элементы и вообще всё, на чём висит `nodata()`.
- Ограничение: одна опция throttling на элемент; после рестарта сервера или правки шагов предобработки последнее значение сбрасывается, и следующее значение не будет отброшено никогда, даже если по правилам должно.
Стенд: частный детский сад на 20 рабочих мест
Разберу на живом примере. Условно — частный детский сад «Капитошка»: 20 рабочих мест у администрации, воспитателей и бухгалтерии, 14 IP-камер с записью на видеосервер, пять точек доступа Wi-Fi, два коммутатора, роутер, один сервер под файлы и бухгалтерию и NAS для архива записей. Мониторинг: Zabbix 7.4 на небольшой виртуальной машине с Debian 12 и PostgreSQL 16, без прокси, 46 хостов, около 2 300 элементов данных, средний NVPS в районе 25. Хранение истории выкрутили на 90 дней «чтобы разбираться с инцидентами», и таблицы истории росли примерно на 1,5 ГБ в месяц. Диск виртуалки — 40 ГБ, к августу свободного оставалось 15 %. Классическая ситуация маленькой организации: денег на расширение не выделяют, а данные жалко.
Первое, что сделал приходящий админ до меня: массовым обновлением навесили шаг Discard unchanged на всё, что отдаёт целые числа. Логика понятная — «зачем хранить 1440 одинаковых единиц в сутки». Объём history_uint за две недели упал на 61 %, прирост базы почти остановился. И одновременно случилось два неприятных события подряд.
Первое — в тот же вечер прилетело 46 писем «нет данных от агента». Элемент agent.ping отдаёт единицу всегда, пока агент жив. С Discard unchanged без heartbeat он сохранил ровно одно значение — первое после включения шага — и замолчал навсегда. Триггер nodata(/host/agent.ping,5m)=1 честно сработал через пять минут по всем хостам разом. Откатили шаг на agent.ping, шторм прекратился, все выдохнули и решили, что проблема была локальной.
Второе вылезло через двенадцать дней и было куда неприятнее. На видеосервере в 03:12 упала служба записи. Элемент service.info["VideoRecorderSvc",state] (имя службы условное), интервал опроса 1m, throttling уже был переведён на Discard unchanged with heartbeat со значением 1h — как показалось, «безопасный компромисс». Напомню, что service.info[...,state] возвращает 0 для running и ненулевые коды для остальных состояний (6 — stopped). Триггер был написан ещё при первичной настройке и выглядел так:
min(/sad-srv01/service.info["VideoRecorderSvc",state],#3)>0Смысл был простой и в общем правильный: «сработать, если три опроса подряд служба не в состоянии running», чтобы не дёргаться на разовый сбой сбора. При минутном опросе это давало реакцию за три минуты. После throttling арифметика поменялась полностью. В 03:12 значение изменилось и легло в базу — одна запись. Две предыдущие сохранённые записи были нулями, записанными по heartbeat в 01:14 и 02:14 (без heartbeat там вообще лежали бы значения недельной давности). Функция min(...,#3) взяла три последние сохранённые записи, среди которых две «нулевые», и вернула 0. Следующий пересчёт триггера возможен только при следующей сохранённой записи — между ними выражение просто не вычисляется. Триггер промолчал. Следующая запись появилась в 04:12 по heartbeat, ещё одна — в 05:12. Только на третьей сохранённой единице окно из трёх значений стало однородным, и проблема наконец открылась. Задержка детекта — два часа вместо трёх минут, и все эти два часа камеры в группах и на входе ничего не записывали.
- Было: опрос 1m, реакция 3 минуты, история около 1,5 ГБ/мес.
- Стало после наивного throttling: экономия 61 % объёма, ложный шторм nodata() и задержка детекта до 2 часов.
- Стало после переделки: heartbeat 5m на дискретных метриках, триггеры переписаны с `#num` на временные окна и `last()`, экономия просела до 43 %, реакция вернулась к 2–4 минутам.
- Итоговый компромисс: 43 % вместо 61 % — это примерно 250–300 МБ в месяц разницы. За два часа без записи с камер в детском саду это смешная цена: запись нужна именно тогда, когда родители спрашивают, что произошло.
«Последние три значения» — это больше не три опроса
Вот главное, что нужно унести из статьи. В синтаксисе функций второй параметр может быть либо sec — период вычисления в секундах, либо #num — максимальный диапазон вычисления в последних собранных значениях. Ключевое слово — «собранных». После throttling собранные значения и опросы перестают быть одним и тем же. Пять последних записей у элемента с минутным опросом могут покрывать пять минут, а могут — три месяца. И вы этого не увидите ни в интерфейсе, ни в графике: график просто нарисует прямую линию между двумя точками, как будто данные были.
Разваливается всё семейство функций с #num: min(), max(), avg(), count(), sum(), last(#N), changecount(), nodata тут отдельная песня. Самый коварный случай — счётчики вида «N подряд». Выражение count(/host/key,#5,"eq","1")=5 до throttling означало «пять опросов подряд», после — «пять последних изменений значения», то есть при бинарной метрике фактически «никогда», потому что она чередуется 0 и 1 и пять единиц подряд в сохранённых данных возникнут только по heartbeat.
Функция change() заслуживает отдельного абзаца, потому что на форуме Zabbix регулярно всплывают жалобы на «непонятно почему не восстанавливаются проблемы» именно из связки change() и throttling. По документации change() — это разница между предыдущим и последним значением. Не «между двумя соседними опросами», а между двумя соседними записями в базе. С throttling предыдущая запись может быть недельной давности. Триггер на change() при этом формально работает, но событие «изменилось» и событие «изменилось только что» перестают совпадать, и выражения восстановления, построенные на симметричном change(), начинают вести себя нелогично.
Что я делаю с этим на практике. Правило простое: у элемента с включённым throttling в триггерных выражениях запрещены функции с параметром #num. Совсем. Либо элемент без throttling — тогда #num разрешён, либо throttling — тогда только временные окна и last(). Для того же случая со службой записи видеосервера правильные варианты выглядят так:
- `last(/host/service.info["VideoRecorderSvc",state])<>0` — срабатывает на первом же сохранённом изменении состояния службы. Для дискретных метрик это почти всегда то, что нужно.
- `min(/host/key,5m)>0` — временное окно вместо счётчика значений; помните, что пустое окно — это не ноль, функция вернёт ошибку и триггер станет unknown, если данных в окне нет вовсе (документация прямо описывает этот случай для функций с периодом).
- `count(/host/key,10m,"eq","1")>=2` — если действительно нужна антидребезговая логика, стройте её по времени, а не по количеству записей.
- Задержку на дребезг лучше уносить из выражения в действие: 'Pause operations for suppressed problems' и эскалация с первым шагом через 3 минуты дают тот же эффект без порчи детекта.
nodata(), таймер history syncer и дырки в trends
Про nodata() документация даёт две цифры, которые надо помнить наизусть. Период не должен быть меньше 30 секунд, потому что процесс history syncer вычисляет эту функцию только раз в 30 секунд, а запись nodata(/host/key,0) запрещена вовсе. Это и есть то самое исключение из правила «нет значения — нет пересчёта»: nodata() считается по таймеру, а не по приходу данных. Страница про триггеры формулирует общее правило так: триггер пересчитывается при каждом новом значении элемента из выражения и дополнительно раз в 30 секунд, если в выражении есть nodata() или функции даты и времени. Именно поэтому связка «Discard unchanged без heartbeat» и nodata() даёт гарантированный ложный сигнал — метрика молчит, а таймер тикает.
Отсюда моё жёсткое правило: если по элементу есть триггер с nodata(X), то heartbeat на этом элементе обязан быть не больше половины X. nodata(...,5m) — значит heartbeat 2m или меньше. Не «примерно», а именно с запасом вдвое, потому что момент сохранения по heartbeat привязан не к границам минут, а к моменту последней записи, и на длинных интервалах опроса набегает дрожание. Если запаса нет — лучше вообще снять throttling с этого элемента, он всё равно почти ничего не экономит.
Второй пострадавший — trends. Тут цитата из мануала однозначная: trends строятся по данным в базе, и если за час не сохранено ни одного значения, то и trends за этот час не будет. Последствия видны не сразу, а через месяц-два, когда housekeeper вычистит историю и графики начнут отрисовываться по трендам. В «Капитошке» это выглядело так: график температуры в серверном шкафу за три месяца превратился в пунктир с провалами по 6–8 часов, а виджет со средним значением за 30 дней стал систематически завышать цифру, потому что в среднее попадали только те часы, когда датчик менял показания, то есть часы активности, а спокойные ночные часы просто отсутствовали.
Практический вывод: если по элементу вам нужны trends — а они нужны почти всем метрикам, по которым вы строите отчёты и SLA, — heartbeat должен гарантировать хотя бы одну запись в час. Формально для этого хватает 1h, но 1h — это ровно граница, и при интервалах опроса, не кратных часу, час без данных периодически проскакивает. Я ставлю 30m и не думаю об этом больше.
- `nodata(X)` на элементе → heartbeat ≤ X/2, иначе снимайте throttling.
- Нужны trends и отчёты → heartbeat ≤ 30m, не 1h.
- Не нужны ни nodata, ни trends, ни оконные функции (справочные элементы: версии, серийники, инвентарь) → heartbeat можно ставить 1d и экономить по-настоящему.
- Элементы с интервалом опроса реже heartbeat — бессмысленны: throttling там не отбросит ничего.
Как я выбираю значение heartbeat
У меня это не эвристика «на глаз», а маленький алгоритм, который применяется к каждому элементу перед включением throttling. Берём самое строгое ограничение из тех, что применимы, и его и ставим. Порядок такой: сначала смотрим, есть ли по элементу nodata(); потом — участвует ли элемент в оконных функциях; потом — нужны ли trends; и только если ни одно не сработало, ставим большое значение ради экономии.
Дальше — про то, как это оформить, чтобы не пришлось потом править шаблон в двадцати местах. Значение heartbeat поддерживает пользовательские макросы, и это надо использовать. В шаблоне я задаю макрос уровня шаблона со значением по умолчанию, а на конкретных хостах при необходимости переопределяю. Фрагмент YAML-экспорта шаблона 7.4 выглядит так:
zabbix_export:
version: '7.4'
templates:
- uuid: a1b2c3d4e5f64a7b8c9d0e1f2a3b4c5d
template: 'Windows services throttling'
macros:
- macro: '{$THROTTLE.HB}'
value: 5m
description: 'Heartbeat для throttling дискретных метрик'
items:
- uuid: 0f1e2d3c4b5a69788796a5b4c3d2e1f0
name: 'Служба записи видеосервера: состояние'
key: 'service.info["VideoRecorderSvc",state]'
delay: 1m
value_type: UNSIGNED
preprocessing:
- type: DISCARD_UNCHANGED_HEARTBEAT
parameters:
- '{$THROTTLE.HB}'Отдельно про интервал опроса. Throttling не отменяет опрос — агент по-прежнему опрашивается раз в минуту, нагрузка на сеть и на pollers остаётся прежней, экономится только запись в базу. Если ваша настоящая цель — снизить нагрузку на сбор, то throttling вам не поможет, надо увеличивать delay или переходить на активные проверки. Я это проговариваю каждому клиенту, потому что путаница между «реже опрашивать» и «реже сохранять» встречается через раз.
- Есть `nodata(X)` → heartbeat = X/2 (округляем вниз до разумного: 5m → 2m, 10m → 5m).
- Элемент участвует в оконных функциях с временным окном W → heartbeat ≤ W/3, иначе окно будет пустым.
- Нужны trends → heartbeat ≤ 30m.
- Ничего из перечисленного, справочная метрика → heartbeat 1d.
- Базовое значение по умолчанию, если думать некогда: 5m. Оно безопасно почти везде и всё равно даёт 80 % экономии на дискретных метриках.
- Минимум по документации — 1 секунда; на практике значения меньше интервала опроса лишены смысла.
Чек-лист для инсталляции, где throttling уже включён
Если вы читаете это уже после включения — порядок действий такой. Сначала инвентаризация: надо понять, на скольких элементах throttling вообще стоит и с какими параметрами. В базе шаги предобработки лежат в таблице item_preproc, коды типов известны из документации API: 19 — Discard unchanged, 20 — Discard unchanged with heartbeat. Запрос на чтение, боевой базе не вредит:
SELECT h.host, i.key_, i.delay,
CASE p.type WHEN 19 THEN 'discard-unchanged'
WHEN 20 THEN 'heartbeat' END AS mode,
p.params AS hb
FROM item_preproc p
JOIN items i ON i.itemid = p.itemid
JOIN hosts h ON h.hostid = i.hostid
WHERE p.type IN (19, 20)
AND i.flags <> 2
AND h.status = 0
ORDER BY mode, h.host, i.key_;Тем, кто не хочет ходить в базу руками, то же самое достаётся через API. Авторизацию в современных версиях делайте API-токеном в заголовке Authorization: Bearer — передача auth в теле запроса устарела:
curl -s -X POST https://zabbix.example.com/api_jsonrpc.php \
-H 'Content-Type: application/json-rpc' \
-H "Authorization: Bearer $ZBX_TOKEN" \
-d '{"jsonrpc":"2.0","id":1,"method":"item.get","params":{"output":["key_","delay"],"selectHosts":["host"],"selectPreprocessing":"extend","monitored":true}}' \
| jq -r '.result[] | select(any(.preprocessing[]?; .type=="19" or .type=="20")) | [.hosts[0].host, .key_, .delay] | @tsv'Дальше — сверка с триггерами. Берём полученный список ключей и ищем по триггерным выражениям вхождения этих ключей вместе с #. Каждое совпадение — кандидат на переписывание. Отдельным проходом ищем nodata( и проверяем, что у соответствующих элементов heartbeat вдвое меньше периода. Это ручная работа на пару часов на средней инсталляции, и её нечем заменить: универсального автоматического правила «как переписать выражение» не существует, потому что смысл каждого триггера знает только тот, кто его писал.
- Выгрузить список элементов с типами предобработки 19 и 20.
- Все элементы с типом 19 (Discard unchanged без heartbeat) перевести на тип 20 — я не оставляю «голый» Discard unchanged нигде, кроме справочных элементов без единого триггера.
- Найти триггеры, где ключ throttled-элемента соседствует с `#`, и переписать на `last()` или временное окно.
- Найти все `nodata(` и сверить с heartbeat по правилу X/2.
- Проверить порядок шагов: throttling — последний в цепочке.
- Проверить зависимые элементы: dependent items получают значение мастер-элемента после его предобработки, поэтому throttling на мастере меняет и то, что доходит до зависимых; ставьте throttling на сами dependent items, а не на мастер.
- После правок дать стенду прожить сутки и сравнить `zabbix[wcache,values]` до и после — понять, сколько экономии осталось.
Что мониторить и когда throttling не нужен вовсе
Раз уж вы полезли в предобработку, заведите заодно наблюдение за ней самой. Внутренний элемент zabbix[preprocessing_queue] показывает количество значений в очереди предобработки — если он устойчиво растёт, у вас не хватает preprocessing workers, и throttling с JavaScript-шагами тут виноваты в первую очередь. Второй полезный элемент — zabbix[wcache,values] с шагом предобработки Change per second: это ваш реальный NVPS, и именно по нему видно, сколько throttling сэкономил на самом деле. Документация прямо рекомендует эту связку.
А теперь честная часть, которую обычно не пишут. Throttling — инструмент для высокочастотного мониторинга. Он придуман для случаев, когда вы снимаете метрики раз в секунду с тысяч устройств, и разница между «писать всё» и «писать изменения» измеряется терабайтами. Если у вас полсотни хостов, как в детском саду с камерами и Wi-Fi, NVPS в пару десятков и база растёт на полтора гигабайта в месяц — throttling вам, скорее всего, вообще не нужен. Дешевле и безопаснее сократить срок хранения истории до 7–14 дней, оставив trends на год, или подключить TimescaleDB со сжатием. Лишние 100 ГБ на виртуалке стоят меньше, чем два часа без записи с камер.
И наоборот, там где риск часто преувеличивают: throttling сам по себе не «портит данные» и не «теряет метрики». График по throttled-элементу рисуется корректно — Zabbix соединяет сохранённые точки, и для ступенчатой метрики это как раз честное отображение. Проблема не в данных, а исключительно в семантике триггерных выражений и в trends. Разберитесь с этими двумя вещами — и throttling будет работать ровно так, как вы от него ждёте.
Мой итоговый рецепт для типичного офиса до 50 рабочих мест: throttling с heartbeat 5m на статусах служб, состоянии дисковых массивов, link status и подобной дискретике; heartbeat 1d на справочных элементах вроде версий и серийников; никакого throttling на agent.ping, внутренних элементах и на всём, где есть nodata(); все триггеры по throttled-элементам — только на last() и временных окнах. Это даёт большую часть экономии и не стоит вам ни одной минуты задержки в детекте реальных аварий.
- `zabbix[preprocessing_queue]` — очередь предобработки, растёт → не хватает preprocessing workers.
- `zabbix[wcache,values]` + Change per second — фактический NVPS, метрика эффекта от throttling.
- `zabbix[process,preprocessing worker,avg,busy]` — загрузка воркеров предобработки в процентах.
- Сравните экономию в гигабайтах с ценой часа простоя — часто ответ «не связывайтесь» оказывается правильным.
Частые вопросы
Какое значение heartbeat поставить, если разбираться некогда?
5m. Оно безопасно почти для любых триггеров, гарантирует наличие trends и всё равно даёт основную часть экономии на дискретных метриках. Менять на большее имеет смысл только для справочных элементов вроде версий ПО и серийных номеров, где нет ни триггеров, ни отчётов.
Почему после включения throttling посыпались триггеры «нет данных»?
Потому что nodata() вычисляется по таймеру history syncer раз в 30 секунд, независимо от того, приходят значения или нет. Метрика вроде agent.ping всегда равна единице, при Discard unchanged без heartbeat она сохраняется один раз и больше никогда — таймер видит отсутствие данных и открывает проблему. Лечится либо heartbeat не больше половины периода nodata(), либо снятием throttling с таких элементов.
Правда ли, что throttling уменьшает нагрузку на сбор данных?
Нет. Опрос выполняется с прежней частотой, pollers и сеть нагружены так же. Экономится только запись в базу и пересчёт триггеров. Если цель — снизить нагрузку на сбор, надо увеличивать интервал опроса или переводить проверки в активный режим.
Можно ли комбинировать Discard unchanged и Discard unchanged with heartbeat на одном элементе?
Нет, документация прямо ограничивает: на один элемент данных допускается только одна опция throttling. Отдельно следите за порядком: мануал не требует ставить throttling последним, но если после него идут другие шаги, он сравнивает промежуточное значение, а не то, что реально попадёт в базу, — я всегда ставлю его в конец цепочки.
Как быстро понять, какие триггеры у меня уже сломаны?
Выгрузите список элементов с шагами предобработки типа 19 и 20 (через SQL по item_preproc или через item.get с selectPreprocessing), а затем найдите триггеры, где ключи этих элементов используются вместе с параметром #num. Каждое такое выражение почти наверняка означает не то, что задумывал автор.
Стоит ли вообще включать throttling в небольшой компании?
Чаще всего нет. Throttling создавался для высокочастотного мониторинга с тысячами устройств. В сети детского сада или небольшого офиса с NVPS в пару десятков дешевле сократить срок хранения истории до 7–14 дней, оставив тренды на год, или включить сжатие в TimescaleDB. Диск стоит дешевле, чем задержка обнаружения аварии.
Источники
- Zabbix Manual (7.0+) — Item value preprocessing, раздел Throttling — Описание шагов Discard unchanged и Discard unchanged with heartbeat: минимум 1 секунда, временные суффиксы, поддержка пользовательских и LLD-макросов, «If a value is discarded, it is not saved in the database… No trigger expressions will be evaluated», ограничение «Only one throttling option can be specified per item», сброс последнего значения после рестарта. https://www.zabbix.com/documentation/current/en/manual/config/items/preprocessing
- Zabbix 7.4 Manual — Appendix 1, History functions, nodata() — Общие параметры функций: sec — период вычисления, #num — диапазон в последних собранных значениях. Для nodata(): «the period should not be less than 30 seconds because the history syncer process calculates this function only every 30 seconds», nodata(/host/key,0) запрещён, режим strict. https://www.zabbix.com/documentation/current/en/manual/appendix/functions/history
- Zabbix 7.4 API — Item object, Item preprocessing — Числовые коды типов (раздел Item preprocessing) предобработки для выборки из базы и через API: 19 — Discard unchanged, 20 — Discard unchanged with heartbeat. https://www.zabbix.com/documentation/current/en/manual/api/reference/item/object
- Zabbix 7.4 Manual — Internal items — Ключи `zabbix[preprocessing_queue]` (количество значений в очереди предобработки) и `zabbix[wcache,values]` с рекомендацией использовать шаг Change per second для получения статистики значений в секунду. https://www.zabbix.com/documentation/current/en/manual/config/items/itemtypes/internal
- Zabbix Blog — Why Zabbix throttling preprocessing is a key point for high-frequency monitoring — Обзорная статья вендора про назначение throttling и про то, что без heartbeat триггеры nodata() будут срабатывать ложно, так как одинаковые данные отбрасываются. https://blog.zabbix.com/why-zabbix-throttling-preprocessing-is-a-key-point-for-high-frequency-monitoring/12364/
- Zabbix Manual — Triggers (Configuring triggers) — Правило пересчёта: триггер пересчитывается при каждом новом значении элемента из выражения и каждые 30 секунд процессом history syncer, если в выражении есть nodata() или функции даты и времени; при отсутствии данных за период функция становится unsupported, триггер — unknown. https://www.zabbix.com/documentation/current/en/manual/config/triggers
