Раннее оповещение о росте очереди Postfix: active, deferred и автоматический разбор причин
Очередь Postfix распухает за минуты до первого тикета в саппорт. Разбираем, почему алерт по абсолютному числу писем в deferred бесполезен, почему active важнее deferred, как построить пороги на скорости роста через Prometheus и как прикрутить к алерту автоматический разбор причины застревания.
EvilMail Team26 июля 2026 г.12 мин чтения
Очередь Postfix — самый честный детектор проблем с доставкой, который у вас есть. Она распухает за пять–десять минут до того, как первый пользователь напишет «мои письма не приходят», и задолго до того, как это заметит внешний blackbox-мониторинг. Проблема в том, что почти все настраивают алерт неправильно: mailq | wc -l > 1000. Такой порог либо молчит во время реальной аварии (потому что при большой легитимной рассылке в deferred и так десять тысяч писем — фон), либо звенит каждую ночь на плановой отправке. В обоих случаях инженер перестаёт доверять алерту, а это хуже, чем его отсутствие.
Ниже — система из трёх принципов. Первое: следим за скоростью роста, а не за абсолютом. Второе: active важнее deferred. Третье: алерт без автоматического диагноза бесполезен — вместе с уведомлением инженер должен сразу получать топ доменов и топ причин застревания, иначе он всё равно полезет в mailq руками в три часа ночи.
Пять очередей Postfix и почему active важнее deferred
Всё, что проходит через Postfix, физически лежит в /var/spool/postfix/ в пяти каталогах:
incoming
— только что принятое от
smtpd
/
pickup
, ещё не разобранное менеджером очереди.
active — то, что qmgr прямо сейчас держит в работе и раздаёт транспортам на доставку.
deferred — письма с временной ошибкой (soft-fail), ожидающие следующей попытки.
hold — то, что вы (или политика) заморозили вручную; Postfix их не трогает.
corrupt — битые файлы конверта, сюда в норме почти ничего не попадает.
active и deferred хешируются по подкаталогам 0–9, A–F, поэтому find по ним быстрый даже на сотнях тысяч писем.
Ключевой тезис всей статьи: deferred — это нормальное рабочее состояние, а растущий active — авария здесь и сейчас. Deferred по определению «отложено до ретрая»: часть писем всегда ждёт — greylisting на принимающей стороне, временные rate-limit'ы, недоступный на минуту хост. Крупная рассылка легко даёт тысячи писем в deferred, и это фон, а не пожар.
А вот active жёстко ограничен параметром qmgr_message_active_limit (по умолчанию 20000). Если active устойчиво растёт — значит qmgr не успевает разгребать: транспорт встал, relayhost умер, упёрлись в default_destination_concurrency_limit (20), или диск/DNS тормозят каждую доставку. Active — это «что происходит сейчас», deferred — «что накопилось».
Цикл ретраев тоже задан дефолтами, и на них мы будем опираться при выборе порогов. Письмо, попавшее в deferred, ждёт минимум minimal_backoff_time (300 с) перед повторной попыткой, интервал растёт до maximal_backoff_time (4000 с ≈ 67 мин), сканер deferred просыпается каждые queue_run_delay (300 с), а живёт письмо до maximal_queue_lifetime (5 дней), после чего возвращается отправителю как bounce.
Снимаем метрики: от postqueue -p до JSON и qshape
Три уровня инструментов — от «быстро глянуть» до «скормить в скрипт».
Быстрый счёт. Итоговая строка postqueue -p даёт суммарный размер и число писем:
bash
postqueue -p | tail -1
# -- 18 Kbytes in 342 Requests.
# отдельно по очередям, минуя парсинг вывода:
find /var/spool/postfix/active -type f | wc -l
find /var/spool/postfix/deferred -type f | wc -l
Структурный разбор. Начиная с Postfix 3.1 есть postqueue -j — построчный JSON, по одному объекту на письмо. Это то, на чём строится авто-диагностика: доступны queue_name, arrival_time, массив recipients[] с полями delay_reason и dsn.
bash
postqueue -j | jq -c '{q:.queue_name, age:.arrival_time, to:.recipients[0].address}' | head
Возрастной и доменный срез.qshape показывает распределение писем по доменам получателя и по возрасту в минутных бакетах (T — младше минуты, затем 5, 10, 20, 40, 80, 160, 320, 640, 1280, 1280+). Строки — домены, отсортированы по объёму:
Домен, «уехавший» в правую часть таблицы (большие бакеты 640, 1280), застрял давно и стабильно. Это ваш «горячий» домен: с ним что-то системно не так, а не разовый сбой. По qshape active сразу видно, размазан ли рост по всем доменам (сдох общий транспорт/relayhost) или упёрся в один (проблема на стороне конкретного получателя).
Экспорт в Prometheus и определение порогов
Руками в qshape ходить не будем — ставим kumina/postfix_exporter. Он читает unix-сокет public/showq (тот же, что postqueue) и параллельно парсит /var/log/mail.log. Запуск с обоими источниками:
postfix_showq_message_age_seconds{queue="active"|"deferred"} — гистограмма возраста писем в очереди (_bucket, _count, _sum); это снимок текущего состояния, а не накопительный счётчик.
Теперь пороги. Никаких абсолютных чисел — только производные и for:, чтобы не ловить одиночные всплески.
yaml
groups:
- name: postfix-queue
rules:
# 1. Active устойчиво растёт 10 минут и уже выше пары сотен
- alert: PostfixActiveQueueGrowing
expr: |
deriv(postfix_showq_message_age_seconds_count{queue="active"}[10m]) > 0
and postfix_showq_message_age_seconds_count{queue="active"} > 500
for: 10m
labels: { severity: critical }
annotations:
summary: "Active-очередь Postfix растёт — qmgr не справляется"
# 2. Голова deferred стареет: P90 возраст > 1 часа
- alert: PostfixDeferredHeadStale
expr: |
histogram_quantile(0.9,
postfix_showq_message_age_seconds_bucket{queue="deferred"}) > 3600
for: 15m
labels: { severity: warning }
# 3. В active вставляют быстрее, чем вынимают
- alert: PostfixQmgrBacklog
expr: |
rate(postfix_qmgr_messages_inserted_total[5m])
- rate(postfix_qmgr_messages_removed_total[5m]) > 5
for: 10m
labels: { severity: warning }
Почему именно так. Порог > 500 для active опирается на дефолты Postfix: в норме qmgr держит active почти пустым, вынимая письма быстрее, чем они приходят; несколько сотен, которые не рассасываются 10 минут при queue_run_delay в 300 с, — это уже не флуктуация. for: 10m покрывает ровно два цикла сканера, поэтому одиночная пачка не поднимет алерт. Для deferred абсолют не трогаем вообще — только возраст головы (histogram_quantile по снимку буферов, без rate, потому что showq-гистограмма не накопительная) и дифференциал insert/remove (backlog копится).
Автоматический разбор причины застревания
Самое ценное — алерт должен приходить уже с диагнозом. Прикручиваем к Alertmanager webhook, который дёргает скрипт-классификатор. Две группировки: по домену получателя и по тексту причины.
bash
# Топ доменов, застрявших в очереди
postqueue -j | jq -r '.recipients[]?.address' \
| awk -F@ '{print $2}' | sort | uniq -c | sort -rn | head
# Топ причин задержки (числа вырезаем, чтобы схлопнуть варианты)
postqueue -j | jq -r '.recipients[]?.delay_reason // "unknown"' \
| sed -E 's/[0-9]{2,}//g' | sort | uniq -c | sort -rn | head
То же самое из лога — иногда причина есть только в said::
Классификация типовых причин — по ней и строится реакция:
450 4.2.0 ... greylisted — greylisting. Ничего не делаем, письмо уйдёт при следующей попытке. Флашить бессмысленно и вредно.
421 4.7.0 ... try again later, 450 4.7.1 rate limited — rate-limit получателя. Снижаем default_destination_concurrency_limit для этого транспорта, иначе будете биться в стену.
Connection timed out, Connection refused — сетевой сбой на приёмной стороне. Проверяем, не наш ли это firewall/маршрут, но чаще это их проблема.
Host or domain name not found. Name service error — DNS. Резолвер лёг или у домена битые MX. Проверяем dig MX и локальный unbound/systemd-resolved.
554 5.7.1 ... blocked using — RBL/репутация. Ваш IP в блок-листе; тушить надо репутацию, а не очередь.
Для суточного среза держите под рукой pflogsumm -d today /var/log/mail.log — он даёт агрегат по deferred/bounced и по причинам за день, удобно вкладывать в утренний отчёт.
Реакция: flush, requeue, hold и когда НЕ трогать
Главное правило оператора: postqueue -f (flush всей очереди) почти всегда вредит. Он заставляет Postfix немедленно передоставить всё из deferred — включая тысячи писем на уже мёртвые адреса и greylisted-получателей, которые всё равно ответят «попробуй позже». Вы устраиваете лавину исходящих соединений, ухудшаете репутацию IP и не ускоряете доставку валидных писем. Flush оправдан ровно в одном случае: relayhost/аплинк только что вернулся к жизни, и вы хотите прогнать накопленное, не дожидаясь queue_run_delay.
Точечные инструменты, которыми пользуются осознанно:
bash
postcat -q QUEUEID # вскрыть конверт и тело конкретного письма
postqueue -i QUEUEID # передоставить ОДНО письмо сейчас
postsuper -H QUEUEID # заморозить письмо (в hold), -h вернуть в очередь
postsuper -r ALL # requeue: пересобрать конверты (после смены конфига)
postsuper -d ALL deferred # удалить весь deferred — только если он реально мусор
Два сценария из практики. Первый: qshape deferred показывает один домен с greylisting в правых бакетах — не делаете ничего, ретрай сработает сам, а любое вмешательство только раздражает их антиспам. Второй: active растёт по всем доменам сразу — это не про письма, это про транспорт. Смотрите relayhost, DNS, TLS-хендшейк, default_destination_concurrency_limit; чинить надо инфраструктуру, а очередь рассосётся сама.
И отдельно проверьте maximal_queue_lifetime и bounce_queue_lifetime. Дефолтные 5 дней означают, что мёртвое письмо пять суток занимает место и ретраится. Для транзакционной почты и temp-mail это обычно слишком долго — на инфраструктуре evilmail.pro осмысленнее 1–2 дня, чтобы очередь отражала актуальное состояние доставки, а не археологию.
Чек-лист внедрения
postfix_exporter поднят с обоими источниками (showq + mail.log) и скрейпится Prometheus.
Алерты на скорость роста active и на дифференциал insert/remove qmgr, а не на абсолютное число писем.
Отдельный warning на возраст головы deferred (P90 > 1 ч) — ловит тихое системное застревание.
К алерту прикручен auto-diagnosis: Alertmanager webhook отдаёт топ доменов и топ delay_reason прямо в уведомление.
Дашборд с разбивкой active/deferred по доменам (данные из qshape/exporter).
Runbook с деревом решений и командами разбора — в описании алерта ссылкой, чтобы дежурный не вспоминал синтаксис postsuper ночью.
Проверены maximal_queue_lifetime и bounce_queue_lifetime — мёртвые письма не живут 5 дней впустую.
Зафиксировано правило: по умолчанию очередь не трогаем; postqueue -f — только после подтверждённого восстановления аплинка.