Этот вопрос проверяет понимание механизмов идемпотентности и способов защиты от дублирования событий.
Чтобы избежать повторной обработки событий, обычно используют идемпотентные ключи, уникальные идентификаторы, таблицы обработанных событий и атомарные операции в БД или Redis. Часто внедряют слой очередей, где событие фиксируется и обрабатывается ровно один раз. Также используют механизмы дедупликации на стороне хранилища, например SETNX в Redis или уникальные индексы в PostgreSQL. Все подходы сводятся к тому, чтобы иметь источник истины: либо внешний ID, либо искусственный ключ, однозначно определяющий событие.
Определение.
Дублирование события — это ситуация, когда внешняя система отправляет одно и то же событие несколько раз (по сети, ретраям, ошибкам доставки).
Причины:
Сервис повторно отправляет webhook при задержке ответа.
Сеть может прервать соединение после обработки.
Очередь может повторно доставить сообщение.
Балансировщик может сделать повторный запрос.
Поэтому система должна быть устойчивой к многократной доставке одного и того же сообщения.
event_idЛучший случай:
Сервис присылает поле event_id.
Мы создаём таблицу processed_events(event_id PRIMARY KEY).
При обработке пытаемся вставить event_id.
Если вставка успешна — событие новое.
Если турнится ошибка «unique violation» — событие дубликат.
Если event_id нет — создаём собственный ключ:
Хеш от содержимого события.
Комбинация бизнес-полей (order_id, event_type, amount).
Храним ключ в БД или Redis.
SETNX)Механизм Redis:
Попытка выполнить SET key value NX EX ttl.
Если ключ установлен — событие новое.
Если нет — дубликат, прекращаем обработку.
Python
if redis.set(key, "1", ex=86400, nx=True):
process_event(event)
Преимущества:
Очень быстро.
Поддерживает TTL, не копится мусор.
Гарантирует атомарность операции.
Можно сделать таблицу:
idempotency(event_id TEXT UNIQUE, processed_at TIMESTAMP)
Логика:
Пытаемся вставить event_id.
В случае ошибки уникальности — событие уже обработано.
Это надёжнее Redis, но медленнее.
Некоторые очереди (Kafka, RabbitMQ + idempotent consumer) поддерживают:
Хранение offset.
Ручное подтверждение обработки.
Гарантии порядка и повторной доставки.
Чтобы избежать повторной обработки:
Храните offset в устойчивой БД.
Обрабатывайте событие только, если offset > last_processed_offset.
Иногда само действие можно сделать идемпотентным:
Примеры:
«Установить баланс на 100» — операция идемпотентна.
«Добавить +100» — нет.
«Обновить статус заказа на PAID» — идемпотентно.
«Создать новую запись транзакции» — нет.
Идея: превратить действие в операцию «set», а не «increment».
На практике обычно объединяют:
Хеш/ID события → таблица/Redis → проверка на дубликат.
Асинхронная очередь → обработчик с фиксирующим offset.
Идемпотентная бизнес-логика.
Комбинация даёт максимальную надёжность.
Чтобы избежать повторной обработки событий, нужно либо иметь уникальный внешний идентификатор, либо создавать его самостоятельно. Далее этот идентификатор фиксируется в БД или Redis, а попытки повторной обработки блокируются. При более сложных сценариях используются очереди и контроль offset. Идеальный вариант — когда и операция, и обработка построены как идемпотентные.