Однаковий success rate — протилежний досвід (де насправді ламається система під навантаженням)
Багато років тому я працював над сервісом прийому факсів через PSTN: користувач купує собі номер у потрібній країні, а всі вхідні факси приходять йому на email у вигляді PDF. Нещодавно — здебільшого з цікавості — я повернувся до тієї системи й промоделював кілька варіантів її архітектури в SimPy.
Один із результатів вартий окремої розповіді. Дві архітектури показали однаковий success rate під час десятикратного сплеску трафіку — але одна з них значно гірша для користувачів. Найцікавіше, що сама метрика success rate цієї різниці принципово не бачить. Розкажу, як влаштована система, що саме я моделював і чому «недорозмірені вхідні двері» можуть виявитись не дефектом, а найкращим захисним механізмом системи.
Система
Конвеєр виглядає так (діаграма нижче):

- Відправник набирає звичайний номер — дзвінок іде через PSTN (публічна телефонна мережа). Ми купували в оператора лінії (T1/E1); лінія — це фіксована кількість одночасних каналів, і саме ця кількість є головним рішенням про місткість системи. Це «вхідні двері», і в них є особливість, про яку далі.
- Processing workers приймають факс і конвертують його в TIFF/PDF.
- Приблизно половина факсів іде далі на OCR — користувачі з платною опцією отримують не картинку, а searchable PDF. OCR — це CPU-bound пул, найважча обчислювальна стадія конвеєра.
- Email delivery відправляє результат на пошту користувача.
SLA у сервісу два: звичайний факс має бути доставлений за 10 хвилин, факс з OCR — за 1 годину. Середнє навантаження — близько 2,8 факса за секунду в робочі години, середня тривалість дзвінка — 90 секунд.
Трафік нерівномірний: два ранкові піки (європейський і американський) дають сплески, які в рази перевищують денне середнє. Тож головне питання розмірності системи — не «скільки вона тримає в середньому», а що відбувається, коли сплеск перевищує її місткість.
Питання, яке я хотів поставити симуляції
Класичне питання capacity planning — «скільки треба ресурсів, щоб витримати навантаження X». Але є й друге питання, яке ставлять значно рідше: де саме система має зламатися, коли X все-таки перевищено?
Це насправді важливо. Точка відмови — це архітектурне рішення, яке проєктується так само, як і пропускна здатність. І, як покаже симуляція, два варіанти з однаковою «кількістю виживших» запитів дають протилежний досвід для людей по той бік системи.
Двері без черги: чому телефонна лінія — особливий ресурс
У телефонної лінії є властивість, якої немає у звичайного бекенд-ресурсу: вона не вміє стояти в черзі. Якщо всі канали зайняті, новий дзвінок не чекає — відправник одразу чує «зайнято» і кладе слухавку. У теорії черг це модель Erlang B: втрати замість очікування.
Скільки каналів треба? Пропоноване навантаження = інтенсивність × тривалість: 2,8 факса/с × 90 с = 250 ерлангів. За формулою Erlang B, щоб тримати блокування на рівні 1 %, потрібно близько 270 каналів. А якщо хочемо пережити десятикратний сплеск без блокувань — уже близько 2 565. Канали коштують грошей, тож спокуса заощадити на «дверях» цілком реальна — питання лише в тому, що станеться, коли сплеск таки прийде.
Модель: три архітектури і їх метрики
Я побудував дискретно-подієву модель у SimPy. Канали — Erlang B: черги немає, блокування миттєве. Processing workers і OCR — звичайні пули з чергою; у теорії черг це позначають M/M/c. Скільки воркерів вільно, стільки факсів обробляється паралельно, решта чекає своєї черги. Далі — sweep: три архітектури × одинадцять рівнів сплеску, від 1× до 10×:
- A — «двері за середнім»: 270 каналів, 35 OCR-воркерів.
- B — «відчинити двері навстіж»: 2 565 каналів, бекенд той самий.
- C — «двері навстіж + підсилити OCR у 5 разів»: 2 565 каналів, 175 воркерів.
Окреме слово про метрики. Якщо під перевантаженням рахувати latency тільки успішних запитів, система виглядає здоровою саме тоді, коли їй найгірше: повільні запити падають по таймауту і зникають зі статистики (survivorship bias). Тому в моделі головна метрика — effective latency: факс, який не вклався в SLA, не зникає зі статистики, а входить у неї зі значенням свого дедлайну — 600 секунд для звичайного факсу, 3 600 для OCR. Це свідомо занижена оцінка: насправді такий факс або приїхав ще пізніше, або не приїхав ніколи.
Тепер про сам показник p95. Це
Звідси два правила читання таблиці нижче. Усі p95 в ній — саме effective. І p95, що дорівнює рівно 3 600 секундам, означає не «доставили за годину», а «показник уперся в стелю»: щонайменше кожен двадцятий факс не вклався у свій дедлайн. Окремо — заблоковані дзвінки в latency не входять зовсім: вони до системи не потрапили і живуть у власній колонці block rate.
Тут потрібна одна домовленість. У моделі черги необмежені, тож факс не втрачається — лише спізнюється: не встиг за годину, приїде за три чи за п’ять. Для розрахунку я спростив ще й це: факс, що прострочив SLA, модель знімає з обробки й далі не рахує — так виходить чітка межа «вчасно / не вчасно» замість нескінченного хвоста.
Обидва спрощення грають на користь B та C. Реальні B і C гірші за модельні одразу з двох причин: знята робота звільняє воркер, якого справжня система в цей момент не звільнила б, і реальні черги скінченні — а переповнена черга це вже не запізнення, а втрата назавжди (саме тому «або не приїхав ніколи» вище — не обмовка). Тобто реальні запізнення в B і C ще більші за таблицю, а частину факсів реальна система втратила б там, де модель лише полічила їх простроченими. Відповідно й success rate у таблиці — це частка факсів, доставлених у межах SLA, а не доставлених узагалі.
Результати

| Сплеск | A: success / block / p95 | B: success / block / p95 | C: success / block / p95 |
|---|---|---|---|
| 1× | 0.98 / 0.8 % / 291 с | 0.98 / 0 / 287 с | 0.99 / 0 / 291 с |
| 3× | 0.84 / 14 % / 288 с | 0.98 / 0 / 748 с | 0.98 / 0 / 520 с |
| 5× | 0.72 / 27 % / 284 с | 0.83 / 0 / 1 734 с | 0.87 / 0 / 717 с |
| 10× | 0.54 / 45 % / 286 с | 0.52 / 0 / 3 600 с | 0.68 / 0 / 1 402 с |
Архітектура A падає швидко прямо на вході. Зі зростанням сплеску Erlang B відсікає дедалі більше дзвінків: 0,8 % → 45 %. Звучить жахливо? Придивіться до третьої колонки: усе, що пройшло у двері, доставлено вчасно — p95 стоїть «як прибитий», ~285 секунд на всьому діапазоні, аж до 10×. А відхилений відправник дізнається про відмову миттєво — і просто набирає номер ще раз.
Архітектура B падає повільно і тихо — всередині. Блокування зникло повністю (колонка block — нулі). Але подивіться на p95: 287 → 748 → 1 734 → 3 600 секунд. Черги тихо забивають бекенд: обробний пул (processing) першим з’їдає success rate — звичайні факси мруть об десятихвилинний дедлайн; а OCR тягне latency вгору, поки p95 не впирається в годинну стелю. На 10× success rate B — 0.52 проти 0.54 в A. Статистично — та сама цифра. Але відмова інша якісно: факс прийнято, відправник пішов у справах упевнений, що все гаразд, — а факс стоїть у черзі й приїде тоді, коли приїде: за годину, за три, за п’ять. Ніхто не передзвонює, бо ніхто не знає. У цьому вся асиметрія: A каже «зайнято» за три секунди, поки людина ще стоїть біля апарата й може набрати ще раз. B не каже нічого — ані відправникові, ані отримувачеві.
Архітектура C: більше заліза допомагає, але не рятує. На середніх сплесках помітно краще (0.87 проти 0.83 на 5×), але на 10× — success 0.68 і p95 1 400 секунд, уп’ятеро гірше за A. І показово, чому саме: C підсилив тільки OCR — його хвіст і справді впав (3 600 → 1 400), — але обробний пул лишився тим самим, і success rate далі тримає він, а не OCR. Нарощування потужності відсуває режим повільної відмови, але не змінює його природи — тим паче коли підсилюють не те вузьке місце.
Урок
Цифра success rate каже, скільки система тягне, — і мовчить про те, де вона ламається. Дві архітектури з однаковим success rate дали користувачам протилежний досвід: швидке чесне «зайнято, спробуйте ще раз» проти тихого запізнення, про яке не дізнається ніхто, поки не стане запізно.
Звідси головний висновок, який я б залюбки отримав на папері ще тоді: недорозмірені вхідні двері — це не дефект, це admission control. Приймаючи роботу, яку не можеш закінчити вчасно, ти перетворюєш швидкі явні відмови на повільні приховані — і навіть не покращуєш success rate.
Так само, як і в житті: відверте «ні» краще за тихо не виконану обіцянку.
Корисно розрізняти дві різні речі, які метрика змішує в одну цифру:
- відмова на вході (admission loss) — відхилено за задумом, миттєво і явно;
- запізнення всередині (congestion delay) — прийнято й доставлено, але тоді, коли вже може бути запізно, і без жодного сигналу.
Перша — це поведінка, яку ви спроєктували. Друга — та, яка трапилася сама.
До чого тут ваші системи (у яких немає факсів)
Телефонний «зайнято» — це найстаріший load shedding у світі, вбудований у саму природу PSTN. У сучасних системах той самий вибір виглядає так:
- обмежений пул з’єднань і відмова одразу — чи необмежена черга повідомлень, яка росте годинами;
- швидкий статус 429/503 з Retry-After на вході — чи запит, що висить до таймаута глибоко всередині;
- backpressure, що доходить до клієнта, — чи буфери, які його «милосердно» приховують, поки не луснуть.
Правило те саме, що й у факсів: якщо системі судилося відмовляти — хай відмовляє на краю, швидко і явно, поки клієнт ще може відреагувати. Прийняти і мовчки не виконати — найгірша з відповідей.
Відтворюваність
Модель, специфікація і повний sweep відкриті: репозиторій twotakt, приклад examples/FaxRx— можна відтворити кожну цифру з таблиці (sweep.py, 33 прогони) і поміняти будь-який параметр. Там-таки verify.py — набір інваріантних перевірок, які мають пересвідчитись що модель рахує правильно.
Сам twotakt — це workflow для побудови SimPy-моделей, яким можна довіряти: спочатку специфікація моделі (MODEL.md), яку затверджує людина, потім код, який верифікується проти неї. Але це вже тема окремої статті.
Обмеження цієї моделі: передзвони не змодельовані — відхилений відправник зникає, а в реальності Erlang-повтори підняли б фактичне навантаження на архітектуру A. Наступне розширення — цикл redial з експоненційним backoff, який покаже, скільки переваги A переживає тиск повторів. Утім, на головний висновок це не впливає: повторний набір — це і є «швидка відмова, на яку клієнт може відреагувати».
Немає коментарів
Додати коментар Підписатись на коментаріВідписатись від коментарів