Как сделать consumer идемпотентным при семантике at-least-once?
- Как обрабатывать дубли сообщений?
Дубли неизбежны при 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 паттерн?
- Как выбрать окно дедупликации?
Разбор темы
- Проектирование событий и команд
Event vs command, обязательные поля envelope, idempotency key, schema evolution, thick vs thin payload и transactional outbox.
Связанные вопросы
Разбор собеседования с sotto
После собеседования sotto разбирает разговор: оценка, каждый вопрос — хорошо, частично или слабо, чего не хватило в ответе и над чем поработать к следующему разу. Первые 30 минут бесплатно.