Real-Time Backend
Delivery Guarantees
В 2012 году Knight Capital потеряла USD 440 млн за 45 минут из-за задвоенных торговых ордеров. Повторная доставка сообщений без идемпотентных обработчиков - не теоретическая проблема.
- Stripe теряет USD X при двойном списании платежа - поэтому API требует Idempotency-Key в каждом запросе на создание charge
- Kafka по умолчанию работает в at-least-once режиме: краш консьюмера до коммита offset приводит к повторной обработке - без idempotency в обработчике данные задвоятся
- MQTT QoS 0 используется в промышленных датчиках температуры: потеря одного показания из тысячи не критична, а низкая задержка важна для реакции системы
- AWS SQS FIFO с MessageDeduplicationId отфильтровывает дубли в 5-минутном окне - это встроенный exactly-once на уровне очереди без дополнительного кода
At Most Once
**At-most-once** - гарантия доставки, при которой сообщение либо доходит до получателя один раз, либо теряется. Повторных отправок нет принципиально: как только сообщение отправлено - забыто. Потеря допустима, дублирование - нет.
Это самая быстрая и дешёвая гарантия - отправитель не хранит состояние, не ждёт подтверждений, не держит retry-буферы. По этой модели работают UDP, UDP-multicast и MQTT QoS 0.
- MQTT QoS 0 - fire-and-forget: брокер не сохраняет сообщение, клиент не получает PUBACK
- UDP-стриминг видео: потерянный кадр просто пропускается, буферизация дороже задержки
- Метрики мониторинга через StatsD: потеря одного счётчика не ломает агрегат за минуту
- DNS UDP-запросы: ретрай на уровне клиента, не транспорта
At-most-once оптимален там, где свежесть данных важнее полноты: телеметрия IoT-датчиков, live-метрики, видеопотоки. Потерять одно показание температуры из тысячи - не критично; задержать всю ленту ради ретрая - критично.
Система собирает показания термодатчиков каждую секунду и строит агрегат за последние 5 минут. Какая гарантия доставки наиболее уместна?
At Least Once
**At-least-once** - гарантия, при которой сообщение доставляется один или более раз. Потери исключены: при сбое или таймауте подтверждения отправитель повторяет. Но дубли возможны - получатель обязан их обрабатывать.
AWS SQS по умолчанию работает по at-least-once: сообщение остаётся в очереди до тех пор, пока consumer не удалит его после успешной обработки. Если consumer упал до удаления - сообщение станет снова видимым через visibility timeout и будет доставлено ещё раз.
- MQTT QoS 1: брокер хранит сообщение до получения PUBACK от подписчика; PUBACK может потеряться - тогда придёт дубль
- Kafka consumer без auto-commit: offset коммитится после обработки; краш до коммита = повторная доставка того же offset
- HTTP webhooks с retry: Stripe повторяет доставку события до 72 часов при ошибках 5xx от получателя
- SQS FIFO с deduplication window: дубли в пределах 5-минутного окна отфильтровываются по MessageDeduplicationId
Ключевое требование at-least-once к consumer - **идемпотентность**: повторное применение операции даёт тот же результат, что и однократное. Запись `SET balance = 100` - идемпотентна. `balance += 10` - нет.
Stripe отправляет webhook `payment.succeeded` и получает в ответ таймаут сети. Stripe повторяет запрос. Какой механизм защищает получателя от двойного списания?
Exactly Once
**Exactly-once** - гарантия, при которой каждое сообщение обрабатывается ровно один раз. Ни потерь, ни дублей. Это самая дорогая гарантия: для её реализации требуется координация состояния между продюсером, брокером и консьюмером.
Kafka реализует exactly-once через два механизма: **idempotent producer** (каждый batch получает sequence number - брокер отбрасывает дубли при ретрае продюсера) и **транзакции** (атомарная запись в несколько партиций + коммит offset консьюмера в одной транзакции). Вместе это даёт EOS - Exactly-Once Semantics.
- Kafka EOS (enable.idempotence + transactional.id): сквозная гарантия producer-broker-consumer в рамках одного Kafka кластера
- Google Pub/Sub exactly-once delivery: каждое сообщение помечается уникальным delivery ID; повторы в пределах 24-часового окна дедуплицируются на стороне сервиса
- Stripe Idempotency Keys: клиент генерирует UUID и передаёт в заголовке `Idempotency-Key`; при повторном запросе Stripe возвращает кешированный ответ без повторного списания
- Distributed transactions (2PC): атомарная фиксация в нескольких системах - дорого по latency, но единственный способ exactly-once across heterogeneous stores
Exactly-once в распределённой системе строго говоря означает **exactly-once processing** при наличии общего хранилища состояния. Гарантия держится до границы системы: Kafka EOS не распространяется на внешнюю БД. Для cross-system EOS нужны idempotency keys или 2PC.
Kafka-консьюмер читает события заказов и записывает их в PostgreSQL. Kafka настроен с EOS (enable.idempotence=true, transactional.id). Гарантирует ли это exactly-once запись в PostgreSQL?
Choosing Guarantee
Выбор гарантии доставки - это trade-off между стоимостью (latency, throughput, сложность), риском потери и риском дублирования. Нет универсально правильного ответа: выбор зависит от природы данных и цены ошибки.
- **At-most-once** - когда потеря дешевле задержки: метрики, телеметрия, live-видео, UDP-игровые события
- **At-least-once** - когда потеря недопустима, а получатель идемпотентен: уведомления, аудит-лог, поиск (переиндексация одного документа дважды не ломает результат)
- **Exactly-once** - когда и потеря, и дублирование приводят к финансовому или логическому ущербу: платёжные операции, списание ресурсов, двойная запись в бухгалтерии
Практический ориентир: большинство систем проектируются с **at-least-once** + **идемпотентными обработчиками**. Это дешевле exactly-once инфраструктуры при той же корректности. Exactly-once оправдан только когда гарантировать идемпотентность обработчика невозможно или слишком дорого.
MQTT QoS 2 реализует exactly-once через 4-way handshake (PUBLISH -> PUBREC -> PUBREL -> PUBCOMP). Это удваивает количество round-trips по сравнению с QoS 1. В IoT-сетях с высоким RTT это ощутимая цена.
Exactly-once всегда лучше - зачем соглашаться на меньшее?
Exactly-once - самая дорогая гарантия по latency, throughput и сложности реализации. Большинство систем достигают той же корректности дешевле: at-least-once + идемпотентные обработчики.
Exactly-once требует координации состояния между всеми участниками (продюсер, брокер, консьюмер) и часто применима только внутри одной системы. Kafka EOS не распространяется на PostgreSQL. Для cross-system EOS нужен 2PC с его высоким latency и риском блокировок. At-least-once + idempotency ключи дают ту же корректность без этих ограничений.
Система отправляет email-уведомления о новых комментариях. Получение двух одинаковых писем раздражает, но не критично. Потеря уведомления тоже некритична. Какая гарантия оптимальна?
Ключевые идеи
- At-most-once: скорость важнее полноты - подходит для высокочастотных метрик и медиастримов, где потеря единичных сообщений не критична
- At-least-once: надёжность через ретраи - получатель обязан быть идемпотентным; это наиболее распространённый режим (Kafka default, SQS standard, HTTP webhooks)
- Exactly-once: максимальная корректность через idempotent producer + транзакции (Kafka EOS) или idempotency keys (Stripe) - ограничена границами одной системы
- Практический выбор: at-least-once + идемпотентный обработчик - оптимальный trade-off для большинства продакшн-систем
Связанные темы
Delivery guarantees пересекаются с несколькими фундаментальными концепциями распределённых систем:
- Idempotency — At-least-once требует идемпотентных обработчиков - без этого повторная доставка ломает корректность
- Message Brokers (Kafka, SQS) — Брокеры реализуют гарантии на уровне протокола: Kafka EOS, SQS visibility timeout, MQTT QoS levels
- Distributed Transactions (2PC) — Единственный способ достичь exactly-once across heterogeneous stores - через двухфазный коммит
Вопросы для размышления
- Сервис отправляет push-уведомления о новых сообщениях в чате. Получатель видит дубль уведомления как баг. Какую гарантию и какой механизм дедупликации стоит выбрать?
- Kafka консьюмер обрабатывает события заказов и пишет в Redis (INCR счётчика) и PostgreSQL (INSERT строки). Как обеспечить корректность при ретраях, если Kafka EOS не распространяется на внешние хранилища?
- MQTT-устройство публикует телеметрию каждые 100 мс. При переключении с QoS 0 на QoS 2 latency вырастает с 5 мс до 40 мс. В каких условиях эта цена оправдана?