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

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

Три ножки сбоя — где реально происходят дубликаты и потери

Суть Как сбои producer-to-broker, durability брокера и broker-to-consumer каждый производят дубликаты или потери, прослеженные через реальный инцидент двойного списания.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на middle-высоте — в небе
◷ 10 min

On-call инженер смотрит на график с 1200 Stripe-списаниями за 900 заказов. Очередь работала шесть месяцев без проблем. Сегодня ночью она списала с клиентов дважды. В коде ничего не изменилось. Сбой был всегда — ждал нужной комбинации медленной Lambda и SQS visibility timeout.

Три ножки сбоя

Сообщение проходит через три отдельные коммуникационные границы. Каждая граница может упасть так, что производит потерю или дубликат.

Ножка 1 — Producer к брокеру. Producer отправляет сообщение и ждёт ack от брокера. Если сеть роняет send — сообщение не дошло. Если сеть роняет ack — producer никогда не узнает, что сообщение сохранено, и ретраит. Брокер получает то же логическое сообщение второй раз. At-least-once producers ретраят на timeout — это feature, не баг, но это значит, что брокер может видеть одно и то же сообщение несколько раз.

Ножка 2 — Durability брокера. Брокер ack-нул producer, но должен сохранить сообщение до краша. Если существует replication lag при краше брокера, followers могут не иметь сообщения. Правильная конфигурация (Kafka acks=all, SQS надёжность по умолчанию) закрывает эту ножку, но misconfiguration тихо роняет сообщения здесь.

Ножка 3 — Брокер к consumer. Именно здесь происходит большинство production-дубликатов. Брокер доставляет сообщение consumer. Consumer обрабатывает (вызывает Stripe, апдейтит БД), потом отправляет ack. Если consumer падает после обработки, но до ack — у брокера нет записи об успехе. Он передоставляет сообщение следующему consumer. Side effect выполняется дважды.

Три ножки сбоя
Producer → BrokerAck потерян → producer ретраит → брокер видит сообщение дважды
Durability брокераКраш до репликации → потеря (config-сбой)
Broker → ConsumerConsumer обрабатывает, потом падает до ack → брокер передоставляет

Трасса инцидента двойного списания

Вот точный SQS visibility-timeout сценарий, который ловит инженеров каждые полгода.

  1. T=0с: Lambda получает msg-7a3f («списать $50 за заказ O-123»). SQS прячет сообщение от других consumer-ов на visibility timeout (дефолт 30с).
  2. T=0.5с: Lambda вызывает Stripe. Stripe списывает карту. charge_id=ch_abc123 возвращён.
  3. T=29с: Lambda всё ещё работает (медленная downstream DB-запись). DeleteMessage ещё не вызван.
  4. T=30с: SQS visibility timeout срабатывает. msg-7a3f становится visible для всех consumer-ов снова — у SQS нет информации, что первая Lambda преуспела.
  5. T=30.1с: Вторая Lambda поднимает msg-7a3f. Вызывает Stripe. Второе списание: ch_xyz789.
  6. T=31с: Первая Lambda наконец вызывает DeleteMessage. Сообщение удалено. Но ущерб нанесён: клиент списан $100 за заказ $50.

Сбой — не баг. Это определённое поведение at-least-once delivery, когда visibility timeout короче времени обработки.

Викторина

Consumer читает сообщение, успешно обрабатывает, но падает до ack. Что делает at-least-once delivery?

Викторина

SQS consumer visibility timeout 10с, но среднее processing 30с. Какой режим сбоя?

Расставь шаги по порядку

Поставь события инцидента двойного списания:

  1. 1 Lambda получает msg-7a3f; SQS устанавливает visibility timeout 30с
  2. 2 Lambda успешно вызывает Stripe — создан ch_abc123
  3. 3 Visibility timeout истекает; msg-7a3f становится visible для всех consumer-ов
  4. 4 Вторая Lambda получает msg-7a3f; снова вызывает Stripe — создан ch_xyz789
  5. 5 Первая Lambda наконец вызывает DeleteMessage — сообщение удалено, но два списания существуют
Вспомните перед уходом
  1. 01
    Что происходит на ножке 3, когда consumer падает после обработки, но до ack?
  2. 02
    Каково rule of thumb для SQS visibility timeout и почему?
  3. 03
    Почему потеря ack на ножке 1 вызывает дубликаты, а не потери?
Итог

У каждой message queue три ножки сбоя: producer-to-broker (потерянный ack вызывает producer retry), broker durability (replication misconfiguration вызывает потерю) и broker-to-consumer (обработка успешна, но ack потерян вызывает redelivery). Самый распространённый production-дубликат на ножке 3: consumer обрабатывает Stripe-charge, падает до SQS ack, visibility timeout срабатывает, второй consumer поднимает то же сообщение и списывает снова. Структурный фикс — идемпотентный consumer с dedup-стором плюс visibility timeout не менее 6x среднего processing time для предотвращения параллельной дублирующей обработки.

Связанные уроки
встречается в178
Продолжить восхождение ↑Consumer-side dedup: самый дешёвый путь к exactly-once processing
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.