Как реализуется идемпотентность при отправке заданий через Kafka
Что проверяют
Понимаете ли вы, что Kafka гарантирует доставку «хотя бы один раз» и дубли гасятся на стороне обработчика, а флаг идемпотентного продюсера этого не решает. Следом обычно спрашивают, где хранить отметки об обработке и когда коммитить offset.
Как отвечать
Разделите задачу на продюсер и консьюмер:
- идемпотентный продюсер:
'enable.idempotence': Trueвconfluent-kafkaилиenable_idempotence=Trueвaiokafka. Брокер отбрасывает дубли от сетевых повторов самого продюсера по идентификатору продюсера и порядковым номерам в партиции. От повторной отправки задачи вашим кодом или после перезапуска процесса это не защищает; - ключ идемпотентности: создаётся один раз вместе с задачей (UUID или бизнес-ключ вроде
order_idплюс тип операции) и едет в заголовке или теле. Ключ сообщения Kafka оставьте под партиционирование, обычно это id сущности, иначе теряется порядок событий по ней; - дедупликация в консьюмере: таблица обработанных ключей с уникальным индексом.
INSERT ... ON CONFLICT DO NOTHINGи сам побочный эффект идут в одной транзакции базы. Вставка не прошла, значит задача уже выполнена; - коммит offset:
'enable.auto.commit': Falseиconsumer.commit(message=msg)после успешной обработки. Упали посередине, сообщение придёт снова, и дедупликация его отсечёт; - атомарная отправка: если задача рождается вместе с записью в базе, нужен transactional outbox. Иначе запись и
produceразойдутся при сбое между ними.
Транзакции Kafka (exactly-once) покрывают только цепочку «прочитал из Kafka, записал в Kafka». Письмо, списание или вызов внешнего API всё равно требуют своего ключа идемпотентности.
Где спотыкаются
Генерируют UUID в момент отправки. Повторная отправка получает новый UUID, и дедупликация по нему бессмысленна.
Хранят обработанные id в set() в памяти процесса. После перезапуска или ребалансировки партиций на другой экземпляр дубли проходят.
Делают проверку и запись раздельно: сначала SELECT, потом обработка, потом INSERT. Два экземпляра успевают пройти проверку одновременно, выручает только уникальное ограничение в базе.