Планування ємності поштового сервера: прогноз росту черги та коли масштабувати
Черга — це випереджальний індикатор, і планувати треба її похідну, а не абсолютний розмір. Розбираємо закон Літтла для MTA, як виміряти реальну drain rate, як відрізнити «розсмокчеться саме» від «лавина» і за якими трьома сигналами масштабувати горизонтально, а не докидати CPU.
EvilMail Team19 липня 2026 р.11 хв читання
Черга росте — але це ще не проблема
Третя ночі, пейджер дзвонить, mailq | tail -1 показує 18 000 листів. Черговий інженер бачить п'ятизначне число, робить висновок «залізо не тягне» і докидає vCPU. За годину черга — 21 000. Ще vCPU. За ранок — 26 000, і хтось нарешті починає читати логи.
Проблема закладена вже в першому питанні. «Скільки листів у черзі» — це майже завжди шум. Значуще питання інше: черга росте, стоїть чи зменшується, і з якою швидкістю? Абсолютне число без похідної не каже нічого. 18 000 листів, що спорожняються за 12 хвилин, — здоровий сплеск. 2 000 листів, що ростуть на 300/хв другу годину поспіль, — це інцидент, який о шостій ранку стане 40 000 і почне впиратися в maximal_queue_lifetime.
Друге, що плутають: active queue vs deferred queue. Deferred — це листи, які ремоут тимчасово відкинув (4xx) і які Postfix перевідправить за backoff-таймером. Роздута deferred сама по собі майже ніколи не аварія — це нормальний буфер для повільних отримувачів. А от зростання active queue означає, що листи надходять швидше, ніж їх вдається віддати просто зараз. Паніка через deferred — типова помилка; дивитися треба на швидкість і на те, у якій саме черзі приріст.
Закон Літтла для поштової черги
Уся інтуїція планування ємності MTA вкладається в одну формулу теорії черг:
L = λ × W
Середня глибина черги (L) = швидкість надходження (λ) × середній час перебування листа в системі (W). Ця рівність виконується для будь-якої стабільної системи, незалежно від розподілу — і саме тому вона така корисна.
Ось що з неї випливає. При сталому припливі λ черга роздувається рівно тоді, коли зростає W — латентність доставки. А латентність доставки — це не про ваш CPU. Вона зростає від деферолів, ретраїв за backoff, greylisting і per-domain throttling на боці отримувача.
Порахуймо. 50 msg/s при середній латентності доставки 4 секунди дає 200 листів «у польоті» — черга дихає, її майже не видно. Той самий трафік 50 msg/s, але Gmail почав тротлити і латентність підскочила до 40 секунд:
L = 50 msg/s × 40 s = 2000 листів у польоті
Приплив не змінився ні на лист. CPU нудьгує. А черга виросла в десять разів — суто через латентність ремоута. Ось чому додавання заліза тут не рятує: ви масштабуєте ресурс, який не є вузьким місцем. Черга — це індикатор латентності, а не навантаження.
Що насправді обмежує пропускну здатність
У drain rate є рівно три стелі, і важливо знати, яка з них ваша.
1. Кількість smtp-процесів.default_process_limit = 100 — стільки одночасних процесів доставки Postfix підніме за замовчуванням. Впертися сюди реально лише на дуже великих обсягах; частіше проблема не тут.
2. Per-destination concurrency.default_destination_concurrency_limit = 20 — скільки паралельних з'єднань Postfix тримає до одного домену призначення. Це ключовий параметр, і він взаємодіє з feedback-механізмом: Postfix стартує з initial_destination_concurrency = 5 і повільно нарощує (positive_feedback = 1) поки ремоут приймає, а на кожен таймаут/відмову — зменшує (negative_feedback = 1). Це вбудований slow-start, і він розумніший за будь-яке ваше статичне число.
3. Rate-limit самого ремоута. Gmail обмежує per-IP за репутацією, Outlook/Office 365 при перевищенні віддає 421 4.7.x (діапазон 4.7.500-699). Це не ваш ресурс. Його не масштабувати ні залізом, ні конкурентністю — його можна тільки поважати. Спроба продавити стелю №3 підняттям стелі №2 дає рівно протилежний ефект: більше 421 → більше deferred → гірша латентність → більша черга.
Читаємо чергу правильно
Перше, що робити о третій ночі, — не докидати ресурси, а порахувати теплову карту:
bash
postqueue -p | tail -1 # розмір черги одним рядком
qshape deferred # домен × вік: 5m 10m 20m 40m ... 1280m+
qshape -s deferred # те саме, відсортоване за розміром
qshape active # що зараз реально в роботі
postconf | grep -E 'concurrency|process_limit|queue_lifetime'
df -i /var/spool/postfix # inode — тихий убивця spool
qshape — головний інструмент. Він розкладає чергу в матрицю «домен призначення × вік у хвилинах». Один рядок, що домінує всю карту, — це один домен-порушник. Рівномірно заповнена карта — системна нестача пропускної здатності. Стовпці ліворуч (5–20 хв) — свіжий сплеск; стовпці праворуч (640m+) — старі листи, що доживають до bounce.
Реальну drain rate рахуємо з логів за фіксоване вікно, не на око:
Отримали, скажімо, sent=2600, deferred=900 за хвилину → drain ≈ 43 msg/s. Якщо приплив у те саме вікно ~50 msg/s — черга росте на ~7 msg/s. Це і є похідна, яку треба екстраполювати. У Prometheus (kumina/postfix_exporter) експортуйте розмір черги за типом, кількість deferred, число smtp/smtpd процесів і розбивку причин деферолу — алерти будуємо на них.
Прогноз: розсмокчеться саме чи це лавина
Модель проста. Якщо drain_rate − arrival_rate > 0, черга спорожниться за:
Якщо знаменник від'ємний — черга розходиться. Тоді лінійна екстраполяція до двох меж: до maximal_queue_lifetime (типово 5 днів, коли найстаріші листи почнуть баунсити) і до вичерпання inode на /var/spool/postfix. Друга межа зазвичай ближча, ніж думають.
Але перш ніж реагувати — класифікуйте за qshape. Три сценарії, три різні дії:
Один домен домінує карту. Ремоут тротлить конкретно вас. Не масштабуйте нічого — виділіть цьому домену окремий транспорт із нижчою конкурентністю і чекайте. Горизонтальне масштабування тут зробить гірше.
Рівномірний ріст усіх доменів. Оце справжня нестача пропускної здатності — суми per-destination concurrency або вихідних IP не вистачає. Кандидат на горизонтальне масштабування.
Сплеск свіжих листів в active (ліві стовпці). Bulk-кампанія, зламаний акаунт або цикл ретраїв застосунку. Шукайте джерело, а не тюньте MTA.
Спершу класифікуй, потім реагуй. Реакція без класифікації — це і є той нічний цикл «докинути vCPU».
default_destination_rate_delay > 0 форсує послідовну відправку в домен із заданою паузою — це навмисний тротлінг під репутацію, і він переважує concurrency_limit. Для «важких» доменів робіть виділений транспорт замість глобального закручування гайок:
Після правки — postmap /etc/postfix/transport && postfix reload. Головне правило тюнінгу: піднімати конкурентність наосліп — це ловити більше 421 і псувати репутацію IP. У 90% випадків правильний рух — вниз, а не вгору. Концентрований транспорт із rate_delay для топ-отримувачів вирівнює латентність краще за будь-яке глобальне число.
Коли і в який бік масштабувати
Вертикально (CPU/RAM/диск) масштабують рідко і тільки за доказом: top показує smtp-процеси, впертий у CPU, або високий IOWait на spool. Тоді допомагає винесення /var/spool/postfix на NVMe. Це справжня, але нечаста ситуація.
Горизонтально масштабують, коли drain уперся в суму per-destination concurrency при вільному CPU. Ключ — репутація: більше чистих вихідних IP = більше паралельних з'єднань без шкоди репутації жодного окремого IP. Це sharding черги, окремі submission/MX-ноди, ротація вихідних адрес. Один IP із concurrency 50 до Gmail отримає бан; п'ять IP по concurrency 10 пройдуть.
Три сигнали, що час масштабувати горизонтально:
Стійкий від'ємний drain понад два повні ретрай-цикли (maximal_backoff_time ≈ 66 хв кожен).
Конкурентність уперлася в ліміт при вільному CPU — стеля не в залізі.
Росте частка 421 / deferred-through-throttle у розбивці причин деферолу.
Чеклист і пороги алертів
Алерт на швидкість росту deferred queue (msg/хв), а не на її абсолютний розмір.
Окремий, різкіший алерт на active queue > N — оце справжня біда, а не deferred.
Тижневий рев'ю qshape -s deferred — які домени стабільно тротлять.
Виділені транспорти з rate_delay для топ-5 доменів-отримувачів.
Canary на drain rate: постійний фоновий підрахунок sent/deferred за вікно, у Prometheus.
Перед будь-яким масштабуванням — qshape-класифікація: один домен, усі домени чи сплеск в active.
Правий важіль майже завжди — не «більше заліза», а «поважати стелю ремоута і розкладати трафік по більшій кількості чистих IP». CPU рідко буває вузьким місцем на MTA; латентність доставки — майже завжди.