Как обрабатывать одновременно пришедшие одинаковые события на разных инстансах сервиса
Что проверяют
Понимаете ли вы, что sync.Mutex и каналы защищают только память одного процесса, а между инстансами нужна координация через базу, брокер или внешнее хранилище. Следом обычно спрашивают: «А что будет, если инстанс упал посреди обработки?»
Как отвечать
Начните с исходной точки: брокеры почти всегда дают доставку «хотя бы один раз», поэтому дубли будут в любом случае. «Ровно один раз» на практике собирается из повторной доставки и идемпотентной обработки. Дальше инструменты:
- идемпотентность через базу: у события есть устойчивый ID, и обработчик в одной транзакции делает
INSERT INTO processed_events (event_id) ... ON CONFLICT DO NOTHINGи бизнес-изменение. Если вставка затронула ноль строк (вpgxэтоRowsAffected() == 0), событие уже обработано, транзакцию откатываем. Это паттерн inbox; - уникальные ограничения: часто хватает уникального индекса на бизнес-ключ, например на номер платежа. База сама не даст записать второй раз;
- партиционирование по ключу: в Kafka события с одним ключом попадают в одну партицию, а партицию в группе читает один консьюмер. Одинаковые события перестают обрабатываться параллельно, но после ребалансировки повторы всё равно возможны;
- блокировки в PostgreSQL:
SELECT ... FOR UPDATE SKIP LOCKEDдля очереди задач в таблице илиpg_try_advisory_xact_lock(key)на время транзакции; - распределённая блокировка в Redis:
SET key token NX PX 30000. Годится как оптимизация, но замок может истечь, пока горутина ещё работает, поэтому для корректности нужен fencing token или проверка версии при записи.
Внутри одного процесса пригодится singleflight из golang.org/x/sync: он схлопывает одновременные одинаковые вызовы в один, но на другие инстансы не влияет.
Где спотыкаются
Отвечают мьютексом или каналом. Это решает гонку горутин, а не двух подов в Kubernetes.
Проверяют «уже обработано?» отдельным SELECT, а потом делают запись. Между проверкой и записью второй инстанс успевает сделать то же самое.
Полагаются на Redis-лок как на гарантию. Долгий сетевой вызов или пауза процесса, и замок оказывается у двух владельцев одновременно.