Идемпотентность операций и дедупликация
↑ 7.3 Надёжность и отказоустойчивость · ← Предыдущая · → Следующая
Зачем это на собесе
Идемпотентность — условие безопасных ретраев и 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.
- Срок хранения ключей ограничен — определите окно дедупликации.
- Идемпотентный ответ должен возвращать тот же результат/статус.
Практика
- Сделайте оплату идемпотентной через unique-индекс и конечный автомат.
- Реализуйте Inbox для потребителя очереди.
- Проверьте гонку двумя одновременными запросами с одним ключом.
Вопросы с ответами
Как сделать POST идемпотентным?
Ключ идемпотентности от клиента и хранение результата по ключу с уникальным ограничением.
Почему проверки «сначала SELECT, потом INSERT» недостаточно?
Между ними возможна гонка; нужен unique-индекс и обработка нарушения.