Идемпотентность операций и дедупликация

↑ 7.3 Надёжность и отказоустойчивость · ← Предыдущая · → Следующая

Желательно~3 мин чтенияУровень: senior

Зачем это на собесе

Идемпотентность — условие безопасных ретраев и at-least-once доставки.

Объяснение

Операция идемпотентна, если повтор даёт тот же результат, что и одно выполнение.

Способы:

СпособПример
Естественная идемпотентностьPUT, DELETE, SET status = 'paid', upsert
Ключ идемпотентностизаголовок Idempotency-Key (см. Идемпотентность и Idempotency-Key)
Дедупликация по ID сообщениятаблица Inbox обработанных сообщений
Условные обновленияUPDATE ... WHERE version = @v (оптимистичная блокировка)
Уникальные ограниченияunique-индекс на бизнес-ключ
Конечный автоматпереход состояния допустим один раз (created → paid)
INSERT INTO payments (id, order_id, amount) VALUES (@id, @order, @amount)
ON CONFLICT (id) DO NOTHING;         -- повтор с тем же id ничего не меняет
 
UPDATE orders SET status = 'paid' WHERE id = @id AND status = 'created';   -- повторная оплата игнорируется

Идемпотентный потребитель:

await using var tx = await db.Database.BeginTransactionAsync(ct);
if (await db.Processed.AnyAsync(x => x.MessageId == msg.Id, ct)) return;
await ApplyAsync(msg, ct);
db.Processed.Add(new Processed(msg.Id));
await db.SaveChangesAsync(ct); await tx.CommitAsync(ct);

Нюансы и подводные камни

  • Проверка «есть ли уже» и запись должны быть атомарными (unique + транзакция), иначе гонка.
  • Внешние побочные эффекты (письмо, платёж провайдеру) — только с ключом идемпотентности провайдера или через outbox.
  • Срок хранения ключей ограничен — определите окно дедупликации.
  • Идемпотентный ответ должен возвращать тот же результат/статус.

Практика

  1. Сделайте оплату идемпотентной через unique-индекс и конечный автомат.
  2. Реализуйте Inbox для потребителя очереди.
  3. Проверьте гонку двумя одновременными запросами с одним ключом.

Вопросы с ответами

Связанные темы