awesome-everything EN
↑ Обратно к восхождению

Очереди, потоки, события

Exactly-once в production: impossibility-доказательство, гибридные паттерны и реальные инциденты

Суть Two Generals доказательство, почему exactly-once delivery невозможна, гибридный Kafka+DB паттерн для effectively-once в масштабе, observability-метрики и реальные сбои Stripe, AWS и Slack.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Stripe 2020: баг во внутреннем queue consumer пропускал Idempotency-Key на retry, списывая небольшой процент клиентов дважды в Q4-пике. Refund в 24 часа. Post-mortem: «exactly-once сложнее, чем кажется; production-корректность живёт в consumer-идемпотентности больше, чем в broker-гарантиях».

Two Generals’ Problem: почему exactly-once delivery невозможна

Два генерала хотят скоординировать атаку на рассвете. Единственная связь — через гонцов по вражеской территории. Гонцы могут быть пойманы. Можно ли договориться?

Доказательство от противного: предположим, протокол существует. Рассмотрим последнее сообщение в протоколе, которое, если доставлено, гарантирует согласие. Если убрать — протокол должен всё ещё работать: отправитель уже имел уверенность до отправки последнего сообщения. По индукции, протокол работает с нулём сообщений — что абсурдно.

Обобщение: никакой конечный протокол поверх ненадёжного канала не может дать обеим сторонам уверенности в общем согласованном факте.

Применительно к очередям: exactly-once delivery требует, чтобы и producer, и broker знали, что сообщение доставлено ровно один раз. Поверх ненадёжной сети — это Two Generals’ Problem. Exactly-once processing требует только одну сторону — consumer — поддерживать dedup-лог. Односторонняя уверенность достижима. Двусторонняя — нет.

Эта асимметрия — причина, почему «effectively-once» — реальная production-цель: at-least-once delivery от брокера, exactly-once processing, обеспечиваемая consumer.

Гибридный exactly-once паттерн в масштабе

Чистая Kafka exactly-once (внутри Kafka topics) не расширяется на внешние системы. Production-grade паттерн для high-throughput payment processor:

  1. Kafka с idempotent producer (enable.idempotence=true, acks=all). Убирает producer-retry дубликаты за ~3% cost.
  2. Consumer at-least-once с isolation.level=read_committed. Видит только committed записи.
  3. Per-message dedup-таблица в Postgres:
    BEGIN;
      INSERT INTO charges (msg_id, status)
      VALUES ('msg-7a3f', 'pending')
      ON CONFLICT DO NOTHING;
      -- если 0 строк inserted: уже обработано, пропустить
    COMMIT;
    -- вызвать Stripe с Idempotency-Key=msg-7a3f
    UPDATE charges SET status='done', charge_id='ch_abc123'
    WHERE msg_id='msg-7a3f';
  4. Stripe Idempotency-Key, derived из msg_id. Stripe хранит первый response 24 часа; любой retry возвращает кешированный response.

Каждый слой независимо тестируем. Система переживает краш в любой точке без двойных списаний. Dedup-таблица также служит audit trail для финансовой сверки.

Гибридный exactly-once: глубокая оборона
Kafka idempotent producerОстанавливает ножку 1 producer-retry дубликатов
DB dedup + ON CONFLICTОстанавливает ножку 3 consumer-retry дубликатов атомарно
Stripe Idempotency-KeyОстанавливает cross-system дубликаты на границе payment API

Observability для delivery-семантики

Без метрик о дубликатах узнаёшь от злых клиентов, не от дашборда.

Per-broker метрики:

  • consumer_lag_messages — publisher offset минус committed offset. Sustained рост означает, что consumer-ы отстают.
  • dlq_depth by source queue — алерт на любое ненулевое значение в production.
  • dlq_age_seconds_p99 — старейшее сообщение в DLQ; алерт при >1 часа.

Per-consumer метрики:

  • dedup_check_hit_rate — как часто dedup INSERT блокируется UNIQUE constraint. Должно быть около 0% в steady state. Пик указывает на агрессивную redelivery: consumer-group rebalance, баг с early ack или broker issue.
  • side_effect_duration_p99 — если превышает visibility timeout, будут дубликаты.
  • retry_count_p99 — sustained высокие значения означают нездоровых consumer-ов.

Distributed tracing: каждое сообщение должно нести trace ID, пропагируемый от producer через consumer в любой downstream сервис. Дублирующая обработка выглядит как два span с одним и тем же message trace ID — мгновенно отличимо от законных отдельных сообщений.

Реальные production-сбои

Stripe 2020: баг internal queue consumer пропускал Idempotency-Key на retry в Q4-пике. Небольшой процент клиентов списан дважды. Refund в 24 часа. Результат post-mortem: ужесточили linter-правило, флагующее HTTP POST в consumer-путях без Idempotency-Key.

AWS SQS 2019: Регион имел race condition visibility-timeout: consumer-ы, вызывавшие ChangeMessageVisibility пока DeleteMessage был in-flight, иногда получали redelivery несмотря на завершение delete. Исправлено в следующем SDK release.

Stripe internal 2023: Горячий DLQ накопил 1M сообщений во время downstream инцидента. Оператор запустил redrive-all без rate limit. Source queue получила 1M сообщений одновременно, перегрузив consumer pool и вызвав cascading throttling на downstream payment API.

Slack 2022: Kafka exactly-once stream pipeline имел баг transaction-coordinator при broker rolling restart. Система доставила дубликаты 6 минут до завершения restart. Root cause: zombie producer session-ы, не proper fenced.

Паттерн во всех инцидентах: корректность нарушилась в consumer или в операционной процедуре, не в broker-гарантии. Broker-гарантии необходимы, но недостаточны; end-to-end корректность требует consumer-идемпотентности и операционной дисциплины.

Викторина

Какова реальная причина, почему Kafka exactly-once не расширяется нативно на внешний Postgres?

Викторина

В steady state dedup_check_hit_rate consumer резко скачет с 0.05% до 8% на 10 минут вне deploy-окна. Наиболее вероятная причина?

Вспомните перед уходом
  1. 01
    Сформулируй Two Generals' Problem и объясни асимметрию, делающую exactly-once processing достижимой.
  2. 02
    Какие три слоя составляют гибридный exactly-once паттерн для payment processor?
  3. 03
    Что означает sustained пик dedup_hit_rate выше 1% вне deploy-окон?
Итог

Two Generals’ Problem доказывает, что никакой конечный протокол поверх ненадёжного канала не может дать обеим сторонам уверенности в согласии — вот почему exactly-once delivery невозможна. Production-системы достигают effectively-once через глубокую оборону: Kafka idempotent producer (~3% cost) убирает producer-retry дубликаты у брокера; consumer-side DB dedup с ON CONFLICT DO NOTHING убирает ножку 3 дубликаты атомарно; Stripe Idempotency-Key убирает cross-system дубликаты на границе payment API. Каждый реальный инцидент — Stripe 2020, SQS 2019, Slack 2022 — подтверждает один паттерн: корректность нарушается в consumer или в операционной процедуре, не в брокере. Мониторь dedup_hit_rate около нуля в steady state; sustained пик вне deploy-окон означает нестабильность consumer, не сбой dedup.

Связанные уроки
встречается в202
Продолжить восхождение ↑Гарантии доставки: тест с выбором ответа
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources5
expand
  1. 01
  2. 02
  3. 03
  4. 04
  5. 05

Trademarks belong to their respective owners. Editorial reference only.