Как построить fail-safe обработку миллионов транзакций в день
Полный текст статьи
1) Что значит fail-safe для транзакций
Fail-safe — это когда любая сбойная ситуация приводит либо к безопасной остановке, либо к компенсируемому состоянию без потери денег и данных. Цели:- «Двойных дебетов/кредитов» = 0.
- Потерянных транзакций/событий = 0.
- Предсказуемые SLO по латентности/доставке, чёткие режимы деградаций и DR.
Основа — денежные инварианты (истина баланса в одном месте), идемпотентность, согласованная доставка событий.
2) Архитектурные принципы (коротко)
1. Single source of truth: баланс и бухгалтерия — в Ledger/Wallet. Сервисы вокруг держат состояния процессов, а не деньги.
2. Idempotency everywhere: все операции «записи» принимают `Idempotency-Key`; повтор возвращает тот же результат.
3. Событийность с гарантией доставки: outbox/CDC, очереди, DLQ, дедуп.
4. Саги и компенсации, а не «ручные правки».
5. Back-pressure и приоритеты: система замедляется, но не рушится.
6. Наблюдаемость по умолчанию: структурированные логи, трейсинг, метрики.
7. Мульти-регион и DR: актив-актив/актив-пасcив, регулярные учения.
3) Референс-топология
Edge/API GW ──Command API ──App Service (Sagas)
│           │
│         (Outbox TX)
RateLimit     Outbox Table ──Publisher ──Kafka/Pulsar ──Consumers
│                      │
WAF                     └─DLQ/Replay
│
└─Ledger/Wallet (ACID, idempotent debit/credit)
│
└─CDC/Changefeed ──DWH/BI/ReconКлючевые места: Outbox (атомарная запись команды и «черновика» события), Publisher (ровно-одна доставка), Consumers (идемпотентны, с дедуп-ключом), DLQ/Replay (контролируемые повторы).
4) Денежные инварианты и согласованность
Истина по балансу — Ledger (ACID, сериализуемые транзакции или строгое упорядочивание по счёту).
Денежные команды: `debit`, `credit`, `hold`, `commit`, `rollback` — идемпотентны.
Комбинированные процессы строятся как саги:- `authorize → settle → credit` (депозит/сеттлмент), `request → submit → settled/failed` (платёж/вывод), `refund/void` (компенсации).
- Никаких прямых правок балансов в обход Ledger.
5) Идемпотентность: дизайн ключей
Ключ должен однозначно идентифицировать бизнес-операцию:- `bet_id+amount+currency`, `payment_intent+capture_id`, `payout_id`, `chain_txid`.
- Хранить результат по ключу (response cache). Повтор с тем же ключом → тот же body/статус.
- Контролируйте несоответствие: одинаковый ключ с другой суммой → `IDEMPOTENCY_MISMATCH`.
6) Очереди, порядок и дедуп
Exactly-once эффекты достигаются не транспортом, а идемпотентными консьюмерами + дедуп-хранилищем (LRU/Redis/DB c TTL).
Сохраняйте порядок по ключу (partition key = `account_id/round_id/player_id`).
Для «разнородных» ключей — версионирование состояния и коммутаторы (state machine per entity).
DLQ обязательна: после N попыток — в изолированную тему с человекочитаемой причиной.
7) Outbox/CDC: почему события «не теряются»
В рамках одной транзакции в БД сервиса записываем и бизнес-изменение, и запись в outbox.
Отдельный publisher считывает outbox и публикует в шину с подтверждением.
Альтернативно — CDC (Change Data Capture) на уровне БД (Debezium/лог репликации).
Никаких «логов событий» мимо транзакции — это источник потерь.
8) Back-pressure и приоритеты
Токен-бакеты и квоты на входе (per tenant/brand/region).
Очереди с приоритетом: денежные пути выше промо/телеметрии.
При перегрузе: режимы `no new sessions/requests`, заморозка вторичных фич, сохранение ядра.
Авто-деградации: урезать частоту фоновых задач, динамически расширять критичные воркеры.
9) Многорегиональная устойчивость
Актив-актив для API и очередей, локальный Ledger (или глобальный с шардингом по региону/валюте).
Data residency: деньги/PII/журналы не кроссируются без явных правил.
Репликация событий межрегионально — асинхронная, с пометкой `region`.
RPO/RTO: целите RPO ≤ 5 минут, RTO ≤ 30 минут; регулярно проверяйте.
10) SLO/SLI и дашборды
Ориентиры (пример):- p95 `authorize/debit/credit` < 150–300 мс (внутренний путь).
- p95 end-to-end «команда→событие в шине» < 1–2 с.
- Доставка вебхуков/внешних событий p99 < 5 мин.
- «Потерянных/дублированных транзакций» = 0 (контрактные проверки).
Метрики: latency p50/p95/p99, error-rate (4xx/5xx/business), consumer/queue lag, retry storms, settle lag, webhook lag, размер DLQ, частота `IDEMPOTENCY_MISMATCH`.
11) Наблюдаемость и аудит
Структурированные логи JSON с `trace_id`, `idempotency_key`, бизнес-ID, кодами ошибок.
OpenTelemetry: трейсинг HTTP/gRPC/DB/шины, спаны саг.
WORM-аудит: неизменяемые журналы критичных изменений (лимиты, ключи, конфиги промо/джекпотов).
Маскирование PII/секретов, региональные бакеты, RBAC/ABAC на доступ к логам.
12) Тестирование надёжности
Контрактные тесты: повтор/дубликаты, out-of-order, идемпотентность, дедуп.
Нагрузочные: профиль пиков (x10), устойчивость очередей и БД.
Хаос-кейсы: падение Ledger/кошелька, отвал очереди/регионов, задержки CDC, «шторм» ретраев.
Game Days: регулярные учения DR и инцидентов, с замером MTTR.
13) Хранилища и данные
OLTP под деньги: транзакционная БД (RPO≈0), строгие индексы, сериализуемые уровни по критичным сущностям.
Кэш (Redis) — только для ускорения, не для «истины». TTL+jitter, защита от cache stampede.
OLAP/DWH — для отчётов/аналитики. Потоки из CDC/шины, без нагрузки на OLTP.
Схемы данных версионируются; миграции без даунтайма (expand/contract).
14) Оркестрация ретраев
Экспоненциальный backoff + джиттер, дедлайны/timeout на RPC.
Идемпотентный повтор на каждом слое (клиент → сервис → потребитель).
Квоты на ретраи, защититься от «штормов» (circuit breaker, hedged requests где уместно).
Replay из DLQ только в «безопасные» окна, с ограничением скорости.
15) Безопасность транспортов
mTLS везде S2S, короткоживущие токены (OAuth2 CC), подписи тел (HMAC/EdDSA) для вебхуков.
Секреты в Vault/HSM, ротация, ключи per brand/region.
Политики least privilege, «четыре глаза» на ручные операции.
16) Примерные контракты (фрагменты)
Идемпотентная команда дебета
POST /v1/wallet/debit
Headers: X-Idempotency-Key: debit_pi_001, X-Trace-Id: tr_a1b2
{
"account_id":"acc_42",  "amount":{"minor_units":5000,"currency":"EUR"},  "reason":"payout",  "reference_id":"po_001"
}
→ 200 { "status":"committed", "entry_id":"e_77" }
(повтор → тот же ответ)Событие из outbox
json
{
"event_id":"uuid",  "event_type":"wallet.debit.committed",  "occurred_at":"2025-10-23T16:21:05Z",  "account_id":"acc_42",  "amount_minor":5000,  "currency":"EUR",  "reference_id":"po_001",  "idempotency_key":"debit_pi_001",  "schema_version":"1.3.0"
}17) Чек-листы
Платформа/оператор
- Истина по балансу — один Ledger; нет обходных путей.
- Все write-операции с `Idempotency-Key`; хранится ответ по ключу.
- Outbox/CDC на все доменные записи, DLQ и управляемый replay.
- Очереди с приоритетами, back-pressure, режимы деградаций.
- Partition-keys выбраны по бизнес-ключам; потребители идемпотентны.
- SLO-дашборды, OpenTelemetry, WORM-аудит.
- Регулярные DR/хаос-учения, контрактные/нагрузочные тесты.
- Data residency, шифрование, Vault/HSM, ротация ключей.
Провайдеры/интеграции
- Отправляю `Trace-Id`/`Idempotency-Key`, готов к повторной доставке.
- Вебхуки подписаны и дедуплицируются.
- Версии схем/контрактов соблюдаются (semver, deprecation).
18) Красные флаги (анти-паттерны)
Баланс меняется по вебхуку без команды в Ledger.
Отсутствие идемпотентности → двойные списания/кредиты.
Публикация событий в обход outbox/CDC.
Монолит без back-pressure: пик трафика валит всё.
Смешение OLTP и отчётов: BI бьёт в боевую БД.
Отсутствие DLQ/реплея; «тихое» проглатывание ошибок.
Нет региональной изоляции PII/денег; общие ключи на несколько брендов.
Ручные правки балансов/статусов в БД.
19) Итог
Fail-safe обработка миллионов транзакций в день — это про инварианты и дисциплину: единый источник истины, идемпотентные команды, саги и outbox/CDC, порядок и дедуп в очередях, наблюдаемость и управляемые деградации. Добавьте мандаты доступа, DR-практики и регулярные учения — и получите систему, где деньги движутся быстро и только один раз, события не теряются, а рост трафика и сбои становятся управляемыми рисками, а не сюрпризами.
