Вам не потрібен метеорит, щоб знищити компанію. Іноді достатньо четвергового ранкового деплою, кількох серверів, що «не отримали повідомлення», і
торгової системи, яка інтерпретує «не робити нічого» як «купувати все».
Якщо ви керуєте продакшн-системами — особливо такими, що можуть переміщувати гроші без запиту дозволу — випадок Knight Capital має бути наклеєний на монітор.
Не тому, що він екзотичний. А тому, що він болісно звичний.
Що насправді сталося (і чому це каскадувало)
1 серпня 2012 року Knight Capital Group розгорнула новий код, пов’язаний із Програмою роздрібної ліквідності Нью-Йоркської фондової біржі (RLP).
За кілька хвилин після відкриття ринку системи Knight почали надсилати потік небажаних ордерів на ринок. Фірма накопичила
величезні позиції, яких не хотіла, і потім у паніці розгорнула їх — закріпивши збитки. Загальна сума, що закріпилася в заголовках: близько $440 мільйонів
втрачено приблизно за 45 хвилин.
Люди люблять підсумовувати це як «помилка в програмному забезпеченні», і це технічно вірно так само, як «Титанік мав проблему з водою» — правда, але неточна.
Механічна відмова включала старий шлях виконання («Power Peg»), який мав бути виведений із експлуатації, контроль у стилі прапорця функції
(«виконувати цю поведінку лише коли увімкнено»), який не був повсюдно застосований, і процес розгортання, що дозволяв серверам працювати з несумісними бінарниками/конфігами.
Операційна відмова була ширшою: недостатній контроль при деплоях, слабкі pre-trade обмеження ризику та незадовільна можливість оперативного вимкнення.
Ось каскадна логіка простою мовою:
- Новий реліз розгорнули на кількох серверах. Деякі сервери не отримали нову версію коректно.
- Новий реліз переназначив прапорець/ідентифікатор, який раніше використовувався для старішої функції.
- На серверах, які не були оновлені, цей переназначений прапорець активував спадщинну функцію замість нової.
- Спадкова функція генерувала агресивну поведінку ордерів повторно, по багатьом символам.
- Контролі ризику Knight не зупинили поведінку достатньо швидко, тому експозиція роздулася до того, як люди встигли відреагувати.
Якщо у вас коли-небудь був «один вузол не оновився» у кластері і ви подумали: «це прикро, але переживемо», — Knight показує, що станеться, коли
радіус ураження системи — це «ринок».
Незручна правда: це було не одна помилка
Knight не впала через одного інженера з поганим днем. Вона впала через сукупність рішень, які в ізоляції виглядали розумно:
повторне використання ідентифікаторів, толерантність до часткового успіху деплоя, залежність від ручних кроків, припущення, що прапорці безпечні,
припущення, що перевірки ризику вловлять «дивну» поведінку, припущення, що люди зможуть випередити автоматику.
Інцидент також нагадує, що «ми вже так робили» не є доказом безпечності. Це упередження з охайною зачіскою.
Жарт №1: перемикачі функцій — як клейка стрічка: геніально, поки вони не є єдиною річчю, що тримає ваш літак разом.
Короткі факти й історичний контекст
Трохи контексту важить, бо подія Knight не була випадковою блискавкою; це була взаємодія між сучасною структурою ринку
і класичною операційною практикою.
- Втрати становили близько $440 мільйонів за приблизно 45 хвилин, переважно через небажані позиції й примусові розвороти.
- Тригер був пов’язаний з Програмою роздрібної ліквідності NYSE (RLP), зміною структури ринку, яка вимагала оновлення систем учасників.
- Knight був великим маркет-мейкером на ринку акцій США, отже його системи були інтегровані у відкриття ринку з великою пропускною здатністю.
- Спадковий шлях виконання («Power Peg») походив із попередньої епохи і мав би бути виведений; він залишався розгортаним і тригерним.
- Частковий деплой — відома небезпека розподілених систем: «роздвоєння версій» може поводитися як дві компанії з одним і тим самим ім’ям.
- Після флеш-крешу 2010 року регулятори і фірми підвищили увагу до автоматичних відсікачів, але внутрішні контролі фірм все ще суттєво відрізнялися.
- У 2012 році багато компаній все ще покладалися на ручні runbooks для деплоїв і аварійних дій; рівень автоматизації був нерівномірним.
- Відкриття ринку — це стрес-тест: волатильність, спрреди й інтенсивність повідомлень зростають, тож помилки, що залежать від часу, і прогалини в безпеці виявляються швидко.
- Виживання Knight вимагало екстреного фінансування і згодом компанію придбали; операційні інциденти можуть бути екзистенційними, а не лише сором’язливими.
Жоден із цих фактів не є «приємним», але вони корисні. Це робить інцидент навчальним: він не екзотичний. Це нормальна організація,
що зустрілася з ненормальним наслідком.
Механіка відмови: деплони, прапорці та неконтрольований потік ордерів
1) Проблема розбіжності версій: «деякі сервери оновлені, деякі — ні»
Розбіжність версій — тихий вбивця у кластерах. Ви думаєте, що маєте «одну систему», але насправді маєте комітет хостів, кожен із яких голосує за реальність
базуючись на локальній файловій системі. У випадку Knight SEC описав деплой, де деякі сервери отримали новий код, а інші — ні.
Вирішальне не те, що це сталося; вирішальне те, що процес дозволив цьому залишатися правдою під час відкриття ринку.
Режим відмови передбачуваний:
- Логіка маршрутизації балансувалася між хостами.
- Поведінка залежить від версії коду, конфігурації або інтерпретації перемикачів функцій.
- Запити потрапляють на різні версії, що призводить до неконсистентних зовнішніх дій.
- Ви бачите «випадкову» поведінку, яка фактично детермінована для кожного хоста.
В трейдингу «неконсистентні зовнішні дії» означають ордери. Ордери — не логи. Їх не відкотиш.
2) Прапорці функцій як контракт API, а не просто тумблер
У історії Knight йдеться про переназначений ідентифікатор, який на деяких серверах викликав стару поведінку. Як би ви це не називали — «прапорець»,
«біт», «режим» чи «магічне значення» — урок однаковий: прапорці є частиною інтерфейсного контракту між компонентами й версіями.
Коли ви повторно використовуєте прапорець, ви робите заяву про сумісність. Якщо будь-який вузол у флоті не погоджується, отримуєте невизначену поведінку.
Операційний урок: прапорці — це не лише інструмент продукту. Це інструмент розгортання. Тому їм потрібні:
- Життєвий цикл (створення, міграція, виведення з експлуатації).
- Перевірки консистентності по флоту.
- Аудитований стан (хто перемкнув що, коли і де).
- Семантика вимикання (disable означає справжню зупинку, а не «зменшити»).
3) Чому pre-trade контроли важливіші за пост-трейд героїку
Багато організацій трактують контролі ризику як пункт у чеклісті для комплаєнсу: «у нас є ліміти». Але в високошвидкісному трейдингу ліміти — це буквально гальма.
Неконтрольована поведінка Knight тривала достатньо довго, щоб створити величезну небажану експозицію. Це вказує на те, що або ліміти були надто вільні,
або надто повільні, або не застосовувалися до потрібних потоків, або не були розроблені для виявлення шаблонів «програмне забезпечення робить не те» (наприклад, повторні агресивні ордери
по багатьом символам).
Ви хочете контролі, які є:
- Локальними: застосовуються близько до точки генерації ордера, а не лише на краю.
- Швидкими: мікросекунди — мілісекунди, а не секунди.
- Контекстними: виявляють незвичні патерни ордерів, а не лише номінальні ліміти.
- Fail-closed: якщо контроль не впевнений — він блокує.
4) Проблема відкриття ринку: найгірший час дізнаватися щось нове
Відкриття ринку — це коли:
- швидкість повідомлень різко зростає (квоти, відміни, заміни),
- спреди швидко змінюються,
- зовнішні майданчики поводяться інакше (аукціони, зупинки торгів),
- оператори дивляться на десять дашбордів одночасно.
Якщо ваша валідація деплою — «ми подивимось, чи щось виглядає дивно», ви фактично вирішуєте тестувати в продакшні, коли продакшн найменш поблажливий.
Моделі відмов, які варто запозичити (для запобігання)
Автоматизація, що біжить нестримно, випереджає людську реакцію
Людина може прийняти рішення швидко. Людина не може наздогнати алгоритм, що генерує тисячі дій за секунду. Будь-який дизайн, що покладається на
«оператори помітять і зупинять», є неповним. Ваше завдання — створити автоматичні тригери, які зупиняють систему раніше, ніж знадобиться людина.
Частковий деплой — категорія інцидентів першого порядку
Ставтеся до «флот неуніфікований» як до інциденту, а не як до дрібної незручності. Вам не потрібно 100% уніфікації постійно, але вона потрібна під час критичних
переходів — особливо коли змінюються семантики прапорців/конфігів.
Спадкова логіка не є безпечною лише тому, що вона «відключена»
Мертвий код, який може бути реаніметований конфігурацією, не мертвий. Це зомбі з правами продакшну.
Якщо ви тримаєте старі шляхи виконання «на всякий випадок», помістіть їх за жорсткими умовами на етапі компіляції або видаліть.
«Ми ніколи не перемикаємо той перемикач» не є контролем.
Одинарні точки відмови можуть бути процедурними
Ми любимо говорити про резервування маршрутизаторів і HA-сторедж, а потім робимо реліз, де одна людина запускає скрипт вручну на восьми серверах.
Це одна точка відмови, тільки з фото бейджа.
Цитата, бо це все ще правда
переказана ідея — John Allspaw: «Бездоганні постмортеми не про звільнення від відповідальності; вони про розуміння того, як робота реально відбувається.»
Інцидент Knight — саме те місце, де та ментальність допомагає: мета — виявити умови, які зробили відмову можливою, а не знайти козла відпущення і
оголосити перемогу.
Швидкий план діагностики
Ви на виклику. 09:30. Торгова команда кричить. Моніторинг показує раптовий стрибок трафіку ордерів і течію збитків у P&L.
Ось як швидко знайти вузьке місце — і, що важливіше, точку контролю.
Перше: зупиніть кровотечу (ізоляція перед цікавістю)
- Запустіть аварійний вимикач для проблемної стратегії/сервісу. Якщо в вас його немає — у вас бізнес-проблема, замаскована під технічну.
- Вимкніть введення ордерів на найближчій точці примусу (шлюз, сервіс ризику або мережевий ACL як останній засіб).
- Заморозьте деплоя і зміни конфігурацій. Єдина допустима зміна — та, що зменшує радіус ураження.
Друге: підтвердьте, чи це розбіжність версій, конфігів чи даних
- Розбіжність версій: чи різні хости виконують різні бінарники/контейнери?
- Розбіжність конфігів: той самий код, але різні прапорці/стани функцій?
- Розбіжність даних: та сама версія і конфіг, але різні стани (кеші, списки символів, таблиці маршрутизації)?
Третє: ідентифікуйте «генератор» ордерів і «підсилювач»
У таких інцидентах зазвичай є компонент, що згенерував першу неправильну дію (генератор), і компонент, що її примножив (підсилювач): ретраї, петлі фейловера,
відтворення черг або логіка «відправити дочірні ордери на батьківський ордер».
Четверте: доведіть безпеку перед повторним увімкненням
Ви не вмикаєте трейдинг тому, що дашборди виглядають спокійно. Ви вмикаєте, бо маєте:
- підтверджено уніфікований флот,
- відомий коректний стан конфігів/прапорців,
- перевірені контролі ризику, які зупинять рецидив,
- поетапний план розгортання з порогами й автоматичним відкатом.
Практичні завдання з командами: виявити, ізолювати, довести
Це не «приємні доповнення». Це вправи на автоматизм. Кожне завдання містить: команду, що означає вивід, і рішення, яке ви приймаєте.
Припустимо Linux-хости з торговим сервісом під назвою order-router, з логами в /var/log/order-router/. Адаптуйте назви під свій світ.
Завдання 1 — Підтвердити, що процес дійсно працює (і де)
cr0x@server:~$ systemctl status order-router --no-pager
● order-router.service - Order Router
Loaded: loaded (/etc/systemd/system/order-router.service; enabled)
Active: active (running) since Wed 2026-01-22 09:22:11 UTC; 8min ago
Main PID: 1842 (order-router)
Tasks: 24
Memory: 612.3M
CPU: 2min 11.902s
CGroup: /system.slice/order-router.service
└─1842 /usr/local/bin/order-router --config /etc/order-router/config.yaml
Значення: Підтверджує шлях бінарника і файл конфігурації. Якщо ви бачите різні шляхи на різних хостах — у вас вже є розбіжність версій/конфігу.
Рішення: Якщо будь-який хост показує інший бінарник або місце конфігурації, негайно ізолюйте його від балансування навантаження.
Завдання 2 — Перевірити хеш бінарника по флоту
cr0x@server:~$ sha256sum /usr/local/bin/order-router
c3b1df1c8a12a2c8c2a0d4b8c7e06d2f1d2a7b0f5a2d4e9a0c1f8f0b2a9c7d11 /usr/local/bin/order-router
Значення: Відбиток бінарника на цьому хості. Зберіть з усіх хостів і порівняйте.
Рішення: Якщо хеші різняться під час інциденту — зупиніться. Зіллйте трафік з невідповідних хостів, потім однотипно перерозгорніть перед відновленням.
Завдання 3 — Підтвердити digest образу контейнера (якщо є)
cr0x@server:~$ docker inspect --format='{{.Id}} {{.Config.Image}}' order-router
sha256:8a1c2f0a4a0b4b7c4d1e8b9d2b9c0e1a3f8d2a9c1b2c3d4e5f6a7b8c9d0e1f2 order-router:prod
Значення: Підтверджує ID образу. Імена на кшталт :prod можуть вводити в оману; digest — істина.
Рішення: Якщо різні хости виконують різні digests під тим самим тегом — вважайте реліз зламаним. Відкатуйте або зафіксуйте на одному digest.
Завдання 4 — Перевірити стан прапорців у джерелі істини
cr0x@server:~$ cat /etc/order-router/flags.json
{
"rlp_enabled": true,
"legacy_power_peg_enabled": false,
"kill_switch": false
}
Значення: Перевіряєте, чи спадкова поведінка справді відключена і чи увімкнений аварійний вимикач.
Рішення: Якщо ви бачите legacy_power_peg_enabled в будь-якому місці — виведіть його або жорстко заблокуйте. Якщо kill_switch відсутній — додайте.
Завдання 5 — Підтвердити консистентність прапорців по хостах (швидке порівняння)
cr0x@server:~$ for h in or-01 or-02 or-03 or-04; do echo "== $h =="; ssh $h 'sha256sum /etc/order-router/flags.json'; done
== or-01 ==
1f9d3f70f1c3b1a6e4b0db7f8c1a2a0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b /etc/order-router/flags.json
== or-02 ==
1f9d3f70f1c3b1a6e4b0db7f8c1a2a0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b /etc/order-router/flags.json
== or-03 ==
9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b9c8d7e6f5a4b3c2d1e0f9a8b /etc/order-router/flags.json
== or-04 ==
1f9d3f70f1c3b1a6e4b0db7f8c1a2a0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b /etc/order-router/flags.json
Значення: Хост or-03 має інший файл прапорців.
Рішення: Негайно виведіть or-03 з обслуговування і усуньте розбіжності в управлінні конфігурацією. Не «чекайте й подивимося».
Завдання 6 — Виявити некерований випуск ордерів у логах (за швидкістю)
cr0x@server:~$ awk '$0 ~ /SEND_ORDER/ {print $1" "$2}' /var/log/order-router/router.log | tail -n 2000 | cut -c1-16 | sort | uniq -c | tail
18 2026-01-22T09:29
22 2026-01-22T09:30
41 2026-01-22T09:31
980 2026-01-22T09:32
1004 2026-01-22T09:33
Значення: Ордерів за хвилину стало від десятків до ~1000. Це не «нормальне відкриття ринку», якщо тільки ви на це явно не налаштовані.
Рішення: Запустіть автоматичне обмеження/вимкнення, якщо швидкість перевищує вивчену базову. Під час інциденту: блокуйте випуск ордерів зараз.
Завдання 7 — Визначити конкретний кодовий шлях/прапорець, що спричиняє поведінку
cr0x@server:~$ grep -E 'POWER_PEG|legacy|rlp_enabled|FLAG_' -n /var/log/order-router/router.log | tail -n 20
412881: 2026-01-22T09:32:01.188Z WARN strategy=RLP msg="legacy path activated" flag=FLAG_27
412889: 2026-01-22T09:32:01.190Z INFO action=SEND_ORDER symbol=AAPL side=BUY qty=200 route=NYSE
412901: 2026-01-22T09:32:01.196Z INFO action=SEND_ORDER symbol=MSFT side=BUY qty=200 route=NYSE
Значення: Логи явно показують активацію спадщинного шляху, пов’язану з ідентифікатором прапорця.
Рішення: Жорстко відключіть спадковий шлях на етапі збірки або блокуйте його під час виконання з охороною, яку неможливо перемкнути в проді без зрозумілого процесу.
Завдання 8 — Підтвердити, які хости отримують трафік (балансувальник або локальний netstat)
cr0x@server:~$ ss -tn sport = :9000 | awk 'NR>1 {print $5}' | cut -d: -f1 | sort | uniq -c | sort -nr | head
42 10.40.12.18
39 10.40.12.21
38 10.40.12.22
Значення: Активні клієнтські з’єднання доходять до цього хоста. Якщо поганий хост активний — він бере участь у відмові.
Рішення: Відтічіть і ізолюйте хост негайно (видаліть з пулу, блокуйте на фаєрволі або зупиніть сервіс).
Завдання 9 — Ізоляція через фаєрвол (останній засіб, але ефективний)
cr0x@server:~$ sudo iptables -A OUTPUT -p tcp --dport 9100 -j REJECT
cr0x@server:~$ sudo iptables -L OUTPUT -n --line-numbers | head
Chain OUTPUT (policy ACCEPT)
num target prot opt source destination
1 REJECT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:9100 reject-with icmp-port-unreachable
Значення: Це блокує вихідний трафік до порту шлюзу біржі (приклад: 9100). Це грубий інструмент.
Рішення: Використовуйте лише якщо на рівні застосунку вимкнення не спрацьовує. Задокументуйте це в каналі інциденту, бо о 2-й ночі ви забудете.
Завдання 10 — Перевірити, що аварійний вимикач справді зупиняє нові ордери
cr0x@server:~$ sudo jq '.kill_switch=true' /etc/order-router/flags.json | sudo tee /etc/order-router/flags.json > /dev/null
cr0x@server:~$ sudo systemctl reload order-router
cr0x@server:~$ tail -n 5 /var/log/order-router/router.log
2026-01-22T09:34:10.002Z INFO flags msg="kill_switch enabled"
2026-01-22T09:34:10.003Z WARN action=BLOCK_ORDER reason="kill_switch" symbol=AMZN side=BUY qty=200
Значення: Система активно блокує ордери й логгує це. Це саме той вивід, який вам потрібен.
Рішення: Якщо ви не бачите явних логів блокування — вважайте, що вимикач не спрацював. Не дискутуйте в умовах невизначеності — ізолюйте на мережевому рівні.
Завдання 11 — Перевірка циклів рестарту і ампліфікації при крашах
cr0x@server:~$ journalctl -u order-router --since "10 min ago" | tail -n 20
Jan 22 09:31:58 server systemd[1]: order-router.service: Main process exited, code=exited, status=1/FAILURE
Jan 22 09:31:58 server systemd[1]: order-router.service: Scheduled restart job, restart counter is at 4.
Jan 22 09:31:59 server systemd[1]: Started Order Router.
Значення: Цикл рестарту може відправляти стартові сплески, відтворювати черги або ініціалізуватися в небезпечних режимах.
Рішення: Якщо лічильник рестартів зростає — зупиніть сервіс і розберіться. «Флапаючий» торговий сервіс — не геройство; це небезпечно.
Завдання 12 — Перевірити, що управління конфігураціями застосоване коректно (виявити дріфт)
cr0x@server:~$ sudo debsums -s
/usr/local/bin/order-router
Значення: Перевірка пакету вказує, що файли відрізняються від очікуваних. Тут вона підсвічує бінарник як змінений/неузгоджений (приклад виводу).
Рішення: Розглядайте дріфт як відмову цілісності релізу. Перевстановіть з надійного репозиторію артефактів; не правте бінарники вручну під тиском.
Завдання 13 — Виміряти затримку шлюзу ордерів (вузьке місце внутрішнє чи зовнішнє?)
cr0x@server:~$ sudo tcpdump -i eth0 -nn tcp port 9100 -c 20
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
09:34:21.112233 IP 10.40.12.10.51234 > 10.50.1.20.9100: Flags [P.], seq 1:145, ack 1, win 501, length 144
09:34:21.114902 IP 10.50.1.20.9100 > 10.40.12.10.51234: Flags [.], ack 145, win 65535, length 0
Значення: Швидкий огляд на рівні пакетів показує, чи повертаються підтвердження вчасно. Якщо ACK-и застопорюються — проблема на мережі/майданчику.
Рішення: Якщо зовнішня затримка стрибнула — застосуйте внутрішнє обмеження; інакше буфери заповняться і ви отримаєте ретраї, що виглядатимуть як «зростаючий попит».
Завдання 14 — Перевірка сховища (логи й черги живуть десь)
cr0x@server:~$ df -h /var/log
Filesystem Size Used Avail Use% Mounted on
/dev/nvme0n1p3 200G 196G 4.0G 99% /var
Значення: Місця на диску майже не залишилося. Логування, збереження черг і навіть запуск процесів можуть вести себе дивно, коли диск заповнений.
Рішення: Звільніть місце негайно (обертання логів, переміщення архівів). Потім виправте політики утримання. «99% заповнено» — це не просто потік даних; це таймер.
Завдання 15 — Підтвердити синхронізацію часу по хостах (порядок подій важливий)
cr0x@server:~$ timedatectl
Local time: Wed 2026-01-22 09:34:30 UTC
Universal time: Wed 2026-01-22 09:34:30 UTC
RTC time: Wed 2026-01-22 09:34:29
Time zone: Etc/UTC (UTC, +0000)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
Значення: Підтверджує, що NTP активний і системний годинник синхронізований. У розподілених інцидентах несинхронні годинники перетворюють дебаг на художню літературу.
Рішення: Якщо будь-який хост не синхронізований, вважайте його ненадійним джерелом доказів; виправте синхронізацію часу перед довірою до кореляцій.
Жарт №2: Найшвидший спосіб дізнатися, що у вас немає аварійного вимикача, — це опинитися в ситуації, де він потрібен.
Три міні-історії з корпоративного життя
Міні-історія 1: Інцидент через неправильне припущення
Середня фінтех-компанія мала сервіс ризику, який валідував ордери перед відправкою на ринок. Сервіс мав режим «тіні» (shadow mode) для міграцій:
у тіньовому режимі він обчислював рішення, але їх не застосовував. Інженери вважали, що тіньовий режим безпечний, бо він нічого не блокував — лише логував.
Під час поспішного релізу вони увімкнули тіньовий режим для підмножини хостів, щоб порівняти поведінку старого і нового рушіїв правил. Та підмножина
була за балансувальником навантаження. Вони вірили, що трафік залишиться рівномірним і виконання залишиться консистентним, бо «деякі хости в тіні не мають значення; інші виконують».
Потім стався зміщення трафіку. Балансувальник став віддавати перевагу «тіньовим» хостам через трохи нижчу затримку. Раптом більшість запитів валідовалася,
але не застосовувалася. Система не «відмовила», вона ввічливо відступила. За кілька хвилин фірма накопичила позиції, що перевищували внутрішні ліміти.
Постмортем був прямим: тіньовий режим не може бути властивістю окремого хоста, коли сервіс за балансувальником. Це має бути стан усього флоту.
Вони змінили дизайн: виконання вирішується централізовано й криптографічно підписується у контексті запиту, і додали жорсткий захист:
тіньовий режим не можна вмикати під час ринкових годин без другої згоди й автоматичного розрахунку радіуса ураження.
Міні-історія 2: Оптимізація, що обернулася проти
Команда інфраструктури торгів оптимізувала канал відправки ордерів шляхом пакетування вихідних повідомлень, щоб зменшити системні виклики.
У бенчмарках виглядало чудово: менше CPU, менше контекстних переключень, гарніші графіки. Вони розгорнули з прапорцем функції. План був поступово збільшувати розмір пакетів.
В продакшні виявився патологічний взаємозв’язок з нижчестоячим шлюзом, який застосовував політику обмеження швидкості повідомлень на з’єднання.
При більших пакетах шлюз приймав TCP-пакет, але затримував обробку, викликаючи відставання підтверджень на рівні додатку.
Вищестоящий сервіс інтерпретував затримку як «потрібно ретраїти», бо логіка ретраїв була написана під втрачені пакети, а не під backpressure.
Тепер підсилювач спрацював. Ретраї створювали більше чергованих повідомлень, що створювало ефективно більші пакети, що спричиняло більше затримки, що викликало більше ретраїв.
Система не впала. Вона стала «ефективною у помилці».
Вони виправили це, розділивши обов’язки: пакетування стало чистою транспортною оптимізацією з жорсткими лімітами, а ретраї стали умовними на основі явних кодів помилок,
а не лише за таймінгом. Також додали сигнал backpressure зі шлюзу: коли затримка перевищувала поріг, введення ордерів обмежувалося і спрацьовувало алертування.
Графіки стали менш приємними. Бізнес спав краще.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Платформа доступу на ринок одного банку мала правило: кожен деплой створював підписаний маніфест зі списком очікуваних хешів файлів, контрольних сум конфігів і прапорців рантайму.
На старті кожен хост перевіряв маніфест і відмовлявся приєднуватися до пулу балансування, поки не пройде перевірку. Це не було захопливою задачею. Це коштувало інженер-години
і затримувало «швидкі правки». Люди постійно скаржилися.
Одного ранку рутинне оновлення частково не вдалося на двох хостах через тимчасову проблему зі сховищем. Ті хости запустилися зі застарілими бінарниками, але свіжими конфігами.
Без маніфестного шлюзу вони б почали обслуговувати трафік — саме такий спліт-брейн, що робить інциденти випадковими.
Натомість вони відмовилися закрито. У пулі було менше хостів, затримка трохи підросла, і спрацював алерт: «хост не пройшов атестацію релізу».
Інженер замінив хости з чистого образу, підключив їх знову, і день пройшов без драм.
Найкраща частина: ніхто поза інфра-командою цього не помітив. Це винагорода за нудну правильність — ваш успіх невидимий, а пейджер тихий.
Поширені помилки: симптом → корінна причина → виправлення
1) Раптовий вибух швидкості ордерів по багатьох символах
Симптом: Кількість ордерів за хвилину зростає в 10–100 разів; символи широкого діапазону; також стрибають відміни/заміни.
Корінна причина: Неконтрольована петля у логіці стратегії, часто спричинена прапорцем, неправильним розумінням ринкових даних або багом ретраїв/backpressure.
Виправлення: Реалізувати жорсткі ліміти по швидкості для стратегії/символу; вимагати явного «озброєного» стану; додати автоматичний вимикач на виявлення аномалій; логувати версію/прапорець, що спричинив ордер.
2) Лише деякі хости некоректні; поведінка виглядає «випадковою»
Симптом: Той самий запит дає різні результати залежно від хоста, що його обробляє.
Корінна причина: Розбіжність версій або дріфт конфігів по флоту; частковий деплой; несумісна інтерпретація прапорців.
Виправлення: Забезпечити атестацію релізу перед приєднанням до пулу; зафіксувати артефакти за digest; постійне виявлення дріфту; припинити використання змінних тегів для критичних сервісів.
3) «Kill switch» перемкнули, але ордери все ще йдуть
Симптом: Оператори вважають, що вони вимкнули торгівлю, але вихідний трафік продовжується.
Корінна причина: Аварійний вимикач реалізований на неправильному рівні (лише UI), не поширений по всьому флоту, кешується або не враховується в «швидкому» шляху.
Виправлення: Розмістіть аварійний вимикач у гарячій ланці з швидкою локальною перевіркою; вимагайте явних логів «BLOCK_ORDER»; забезпечте поза-канальну мережеву блокувальну процедуру.
4) Контроли ризику не спрацювали, поки збитки не стали величезними
Симптом: Ліміти є на папері, але неефективні під час швидкої відмови.
Корінна причина: Ліміти занадто грубі (добові номінали), занадто повільні, не застосовуються по стратегії або залежать від відкладеної агрегації.
Виправлення: Додайте мікро-ліміти: по-символьні номінали, ліміти за хвилину на кількість ордерів і обіг, співвідношення відмін/виконань; fail-closed коли телеметрія застаріла.
5) Спадкова функція «відключена», але раптом реанімується
Симптом: Логи показують, що виконується старий шлях; інженери присягаються, що він мертвий.
Корінна причина: Мертвий код за runtime-прапорцями, повторне використання ідентифікаторів або конфігурація, що може бути випадково перемкнена.
Виправлення: Видаліть мертвий код; заблокуйте спадкові шляхи на етапі компіляції; резервуйте ідентифікатори; вважайте повторне використання прапорців як зміну, що ламає сумісність і вимагає уніфікації флоту.
6) Реакція на інцидент гальмується, бо ніхто не довіряє дашбордам
Симптом: Команди сперечаються про реальність: метрики не погоджуються, часові позначки не збігаються.
Корінна причина: Проблеми синхронізації часу, невідповідні назви метрик, відсутність контролю кардинальності або семплінг, що приховує сплески.
Виправлення: Забезпечити NTP; стандартизувати схеми подій; будувати «чорний ящик» ордерів (order flight recorder); валідувати моніторинг під час спокійних періодів за допомогою синтетичних канарейок.
Чеклісти / покроковий план
Чекліст безпеки релізу (для будь-якої системи, що може рухати гроші)
- Незмінність артефактів: збирати один раз, підписувати, розгортати по digest/hash. Жодних змінних тегів «latest/prod» як єдиного джерела істини.
- Гейт уніфікації флоту: хости перевіряють контрольні суми бінарників і конфігів, перш ніж приєднатися до service discovery/load balancer.
- Політика життєвого циклу прапорців: прапорці мають власників, терміни життя і правила «не повторно використовувати ідентифікатори»; виведення з експлуатації — це запит на зміну.
- Безпечні дефолти: нові шляхи коду стартують відключеними і не можуть активуватися без валідації стану конфігу й контролю «озброєний».
- Pre-trade гарди: ліміти швидкості, номінальні капи та детектори аномалій, що застосовуються локально в сервісі генерації ордерів.
- Поетапне підняття: увімкнення по стратегіях, по майданчиках, по наборах символів; підняття з порогами; автоматичні тригери відкату.
- Dry run у продакшні: тіньове обчислення дозволено тільки якщо воно не змінює зовнішню поведінку і консистентне по флоту.
- Дрілли інцидентів: практикуйте аварійний вимикач, відтік трафіку і відкат щотижня. Навички деградують швидко.
Операційний чекліст ізоляції (коли все вже горить)
- Активуйте аварійний вимикач (рівень застосунку) і підтвердьте явними логами «blocked».
- Відтічіть трафік з підозрілих хостів; ізолюйте невідповідні версії негайно.
- Блокуйте на краю, якщо потрібно (вимкнення шлюзу або фаєрвол) з записаною нотаткою про зміну.
- Зніміть знімки доказів: зберіть хеші, конфіги й логи перед перезапуском у чистий стан.
- Припиніть рестарти: цикли крашу підсилюють шкоду; стабілізуйте спочатку.
- Відновіть відомий добрий реліз з підписаних артефактів; не правте бінарники/конфіги вручну під тиском.
- Повторно ввімкніть поступово з жорсткими порогами і автоматичним механізмом тригеру.
План затвердження інженерних посилень (що реалізувати, в порядку)
- Аварійний вимикач з підтвердженням: повинен зупиняти нові ордери за секунди; повинен генерувати підтверджуючі логи/метрики.
- Атестація релізу: жоден хост не обслуговує трафік, поки не підтвердить, що працює зі штатним кодом/конфігом.
- Гальма за хвилину: реалізувати ліміти швидкості й експозиції близько до генератора.
- Управління прапорцями: інвентаризація прапорців, заборона повторного використання, видалення старих шляхів.
- Канарка з можливістю abort: один хост, потім 5%, потім 25% і т.д., з автоматичними умовами відкату.
- Політика змін під час ринку: обмежити ризикові перемикання; вимагати двох осіб для затвердження всього, що може змінити поведінку торгів.
- Чорна скринька (flight recorder): незмінювана стрічка аудиту «чому цей ордер стався» з версією, прапорцями і підсумком оцінки правил.
Питання й відповіді
1) Чи був інцидент Knight Capital просто «поганим деплоєм»?
Деплой був тригером. Масштаб втрат — продукт відсутності утримуючих заходів: неконсистентність стану флоту, доступність спадкової поведінки
і контролі ризику, що не зупинили некерований генератор ордерів достатньо швидко.
2) Чи міг сучасний CI/CD це запобігти?
CI/CD допомагає, якщо він забезпечує незмінність артефактів, атестацію і поетапні розгортання з автоматичними відкатами. Швидший пайплайн без гейтів безпеки
просто дозволить швидше доставити поламане.
3) Чому повторне використання прапорця або ідентифікатора таке небезпечне?
Тому що це контракт сумісності. Якщо будь-який вузол інтерпретує цей прапорець інакше (стара бінарка, стара схема конфігу), ви створили розподілене
семантичне роздвоєння: ті самі вхідні дані — різна поведінка.
4) Який єдиний найкращий контроль додати для торгових систем?
Справжній аварійний вимикач, який застосовується в шляху генерації ордерів і ефективність якого можна довести логами/метриками. Не чекбокс у UI.
Не пропозиція в runbook.
5) Хіба для цього не існують ринкові circuit breakers?
Біржові circuit breakers захищають ринок. Вони не захищають вашу фірму від вашої автоматики. Вам потрібні внутрішні pre-trade контроли,
обмежувачі і гальма, специфічні для стратегії.
6) Як автоматично виявити «частковий деплой»?
Вимагайте, щоб кожен хост пред’являв ідентифікатор релізу (хеш бінарника, контрольну суму конфігу, номер схеми прапорців) і нехай service discovery відмовляє
в реєстрації, якщо не збігається. Також запускайте безперервне виявлення дріфту, що викликає пейдж, коли з’являється невідповідність.
7) Чи завжди треба видаляти спадковий код?
Якщо спадковий код може змінювати зовнішню поведінку (відправляти ордери, переміщувати гроші, видаляти дані) і його можна активувати конфігурацією — видаліть або жорстко виключіть під час збірки.
«Відключено» не є властивістю безпеки.
8) Що робити, якщо зміни треба застосувати під час ринкових годин?
Тоді робіть їх нудними: лише перемикачі з жорсткими обмеженнями радіуса ураження, двоособовим підтвердженням, миттєвим відкатом і автоматичним виявленням аномалій, що спрацьовує за секунди.
І практикуйте відкат, поки він не стане м’язовою пам’яттю.
9) Як SRE і інженерія сховищ пов’язані з торговим збоєм?
Торгові інциденти часто підсилюються буденними інфраструктурними проблемами: лог-диски заповнюються, черги накопичуються, годинники йдуть не в ногу або цикли рестарту повторюють повідомлення.
Інженерія надійності — це спосіб не дати «одній помилці» перетворитися на «загрозу існуванню фірми».
Наступні кроки, які можна реалізувати цього кварталу
Якщо ви експлуатуєте системи, що можуть розміщувати ордери, переміщувати кошти або запускати незворотні зовнішні дії, сприймайте історію Knight Capital як вимогу до дизайну:
ваша система має падати закритою (fail-closed), залишатися консистентною під час деплою і надавати миттєвий механізм зупинки, що не залежить від надії.
Зробіть це далі, у такому порядку
- Реалізуйте перевірний аварійний вимикач (логи блокованих, метрики і команда для верифікації одним натисканням).
- Додайте гейти атестації релізів, щоб частковий деплой не міг обслуговувати трафік.
- Впровадьте гальма за хвилину (ліміти швидкості і номіналу) близько до генератора ордерів.
- Аудитуйте й виводьте спадкові прапорці/шляхи коду, що можуть реанімувати стару поведінку.
- Практикуйте інцидентні вправи з часовими лімітами: «ізолювати протягом 60 секунд» — добрий початковий бар.
- Впровадьте поетапні релізи з умовами abort, заснованими на аномаліях швидкості ордерів і патернах відмов.
Історія Knight Capital не лякає тим, що вона унікальна. Вона лякає тим, що вона знайома: несумісні сервери, ризиковий перемикач і система, створена діяти швидко.
Якщо ваші контролі повільніші за вашу автоматизацію, у вас немає контролів. У вас є паперова документація.