Вопрос с собеседования · Брокеры сообщений

Как сделать consumer идемпотентным при семантике at-least-once?

MiddleПроектирование событий
Ещё спрашивают так:
  • Как обрабатывать дубли сообщений?
Коротко

Дубли неизбежны при at-least-once, поэтому обработчик делает повтор безвредным: проверка id сообщения в таблице обработанных в той же транзакции, что и эффект, или естественно идемпотентные операции.

  • Таблица processed_messages(message_id PRIMARY KEY) + бизнес-изменение одной транзакцией
  • UPSERT, «установить статус», запись с версией вместо «прибавить»
  • Ключ дедупликации — id события от издателя, а не offset

А ещё Внешние эффекты (письмо, платёж) — ключ идемпотентности у внешнего API

Подробно

Почему нужно: сообщение может прийти повторно после сбоя потребителя, ребаланса, повтора продюсера. «Прибавить 100 рублей» дважды — уже ошибка.

Способы:

  • дедупликация по id — каждое событие несёт уникальный event_id. Обработчик в одной транзакции вставляет id в таблицу обработанных (уникальный ключ) и делает изменение. Повтор упадёт на уникальности — пропускаем. Записи хранят ограниченное время;
  • естественная идемпотентность — операции, повтор которых ничего не меняет: UPSERT по ключу, «установить статус оплачен», «записать последнее значение»;
  • версии — применять событие, только если его версия больше сохранённой для агрегата (заодно защищает от перестановок);
  • внешние вызовы — передавать ключ идемпотентности во внешний API (платёжный провайдер не спишет дважды).

Важная деталь: проверка и эффект — атомарно. «Проверить в Redis, потом записать в базу» — гонка и окно для дубля при падении между шагами.

Пример

BEGIN;
INSERT INTO processed_events(event_id) VALUES ($1);   -- дубль упадёт здесь
UPDATE accounts SET balance = balance + $2 WHERE id = $3;
COMMIT;

Что хотят услышать

  • Почему дубли неизбежны.
  • Дедупликация по id в одной транзакции с эффектом.
  • Естественно идемпотентные операции и версии.
  • Внешние эффекты с ключом идемпотентности.

Частые ошибки

  • Проверять дубликат и применять эффект в разных хранилищах без атомарности.
  • Дедуплицировать по offset.

Проверьте себя

Один верный вариант. Подумайте, потом откройте ответ.

Показать варианты и ответ
  • ✓Хранить id обработанных сообщений в транзакции с эффектом
  • ✗Включить exactly-once в настройках потребителя — тогда дублей не будет при любой обработке
  • ✗Дедуплицировать по offset сообщения в памяти процесса потребителя, без записи в базу
  • ✗Коммитить offset до обработки, чтобы повторы стали невозможны в принципе

Что спросят дальше

  • Что такое inbox/outbox паттерн?
  • Как выбрать окно дедупликации?

Разбор темы

Связанные вопросы

Партнёр платформы

Разбор собеседования с sotto

После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.

Разбор собеседования