Idempotency и deduplication¶
Раздел 04 · Типы достоверности
Тип материала: инженерный разбор. Схемы и примеры иллюстративные. Рекомендации — INFERENCE; учебные параметры — HYPOTHETICAL.
Проверка источников: 2026-11-02. Версии и границы первоисточников перечислены в конце. Documented contracts не означают выполненный runtime test.
Принцип работы и границы¶
Idempotency — повтор одной операции не меняет уже зафиксированный бизнес-результат. Deduplication — обнаружение повторяющихся identities. Это разные задачи: DELETE по ключу может быть идемпотентным без inbox; marker отдельно от изменения баланса может удалить необходимый повтор после crash.
Для команды выберите ключ (tenant_id, operation_id), canonical payload hash и сохранённый результат. Повтор с другим payload — конфликт, а не успешная дедупликация. Inbox identity, effect и response должны фиксироваться атомарно. FACT: PostgreSQL уникальные индексы проверяют конфликтующие вставки; CREATE INDEX 17. INFERENCE: соперничающие requests разрешаются DB constraint, а не предварительным SELECT в приложении.
Transport identity (topic, partition, offset) полезна для точного broker replay, но повторная публикация той же бизнес-команды имеет другой offset. Поэтому business identity переносится через всю цепочку.
Иллюстративная архитектура¶
flowchart TD
R[Request: tenant, operation, hash] --> U[Unique operation key]
U -->|new| TX[Effect and saved response in transaction]
U -->|same payload| OLD[Return saved response]
U -->|different payload| Q[Conflict quarantine]
Failure scenarios¶
Crash после INSERT marker в отдельной транзакции теряет effect навсегда. Crash после effect перед marker удваивает сумму. TTL inbox раньше самого старого допустимого replay открывает окно повторного списания. Неатомарная проверка payload при конкурентном duplicate может вернуть результат чужой команды.
Recovery protocol¶
- Для ambiguous outcome повторить исходный ID на authoritative primary.
- Если identity найдена, сверить canonical hash и вернуть сохранённый результат.
- Если записи нет, попытаться выполнить ту же атомарную операцию; unique constraint разрешит гонку с ещё живой первой попыткой.
- При конфликте payload блокировать автоматический replay и сохранить evidence.
- Перед очисткой inbox доказать закрытие replay horizon, включая архив, DLQ и manual corrections.
Trade-offs и критерии выбора¶
Долгий inbox увеличивает storage/index cost. Ограниченный dedup horizon подходит кешируемой projection, если её можно целиком перестроить. Для денежных operation IDs очистка требует отдельного бизнес-контракта. Хеш не заменяет сохранённый ответ и не задаёт domain identity.
FinTech и iGaming¶
Payment retry и повтор webhook должны сходиться к одной проводке. Для ставки ключ приёма и ключ settlement revision различны: исправление результата — новая допустимая операция, а не duplicate старой.
Практический пример¶
Существующий projection.py фиксирует inbox и totals в одной SQLite transaction и отклоняет payload mismatch. Используйте lab для crash/reopen/replay. Это reporting effect, не доказательство correctness банковского ledger.
Типичные ошибки проектирования¶
Глобальный ключ без tenant namespace; случайный UUID на каждый retry; сравнение JSON без canonicalization; сохранение inbox в Redis при финансовом эффекте в PG.
Вопросы на собеседовании¶
Можно ли считать SETNX достаточным? Только для защищаемой Redis-операции в определённой durability boundary; он не атомарен с внешней БД.
Повтор с иным amount? Conflict: не исполнять, не возвращать молча старый success как новый результат.
Дополнительные источники¶
- PostgreSQL isolation — 17.
- Redis persistence — rolling; examples 7.4.