Перейти к содержанию

F. Ledger concurrent updates и double-spend

Раздел 04 · Failure matrix

Тип материала: учебный архитектурный сценарий. Не описывает систему конкретной компании. Все операции, IDs и числа ниже — HYPOTHETICAL.

Проверка источников: 2026-11-02. Интеграционный fault test этого сценария — NOT_RUN; documented behavior и предлагаемый recovery не считаются выполненным испытанием.

Normal flow

PostgreSQL 17 — authoritative ledger; accounts одной tenant/currency находятся в одной transaction domain. Команда debit имеет operation_id, request hash и amount_minor. Locks берутся в детерминированном порядке; проверка available funds и postings фиксируются вместе. Авторизация по stale Redis balance недопустима.

Иллюстративная схема нормального пути:

flowchart LR
 C[Command with operation ID] --> L[Lock accounts in stable order]
 L --> V[Check funds and invariant]
 V --> P[Postings, balances, response and outbox]
 P --> D[One DB commit]

Failure timeline

sequenceDiagram
 participant A as Request A
 participant B as Request B
 participant D as PostgreSQL
 A->>D: Read available balance
 B->>D: Read same old balance
 Note over A,B: Unsafe application precheck alone
 A->>D: Lock and guarded debit
 B->>D: Wait then recheck current funds
 A->>D: Commit balanced postings
 D-->>B: Reject if insufficient after lock released

Состояние систем после отказа

Участник Durable состояние Неопределённость / проверка
Ledger Один transaction commit или rollback Проверить postings, available и operation result
Competing request Может ждать lock или получить serialization failure После abort вся business transaction повторяется
Outbox/analytics Видит только committed operation Projection не авторизует второй debit

Retry и replay

Одинаковый operation_id обязан вернуть прежний результат; разные IDs — разные business commands, обе должны заново проверить funds. SERIALIZABLE может abort одну transaction; retry повторяет её целиком с тем же ID. CHECK balance>=0 на одном счёте не доказывает debit/credit conservation или multi-account limit.

Обнаружение проблемы

Проверить negative available, duplicate operation keys, несбалансированные postings по transaction/currency, lock waits/deadlocks и account histories. Зелёные HTTP responses не являются oracle. Отдельно проверить, не произошло ли внешнее действие до ledger commit.

Recovery protocol

  1. При нарушении invariant остановить новые spending actions affected scope.
  2. Сохранить transaction/operation evidence и определить авторизованные commits.
  3. Сверить postings и external settlement, не удалять финансовую историю.
  4. Применить approved compensating postings для подтверждённых ошибок.
  5. Исправить locking/guarding и проверить две concurrent sessions, rollback и retries.
  6. Возобновить после key-level и balance reconciliation.

Гарантии и trade-offs

Row locking/guarded debit защищает локальный invariant при корректной полной transaction. Это не распределённый ledger всех валют/шардов и не гарантия external payment uniqueness. Local SQLite tests не проверяют PG MVCC или concurrent double-spend; PG runtime NOT_RUN.

Практическая проверка

PG concurrent lab ссылается на исходную ledger модель. Сначала воспроизведите unsafe precheck, затем запретите его guarded update и replay stable operation identity.

Источники и версии