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 мс. В каких условиях эта цена оправдана?

Связанные уроки

  • db-03-acid
Delivery Guarantees

0

1

Войти