F. Ledger concurrent updates и double-spend¶
Тип материала: учебный архитектурный сценарий. Не описывает систему конкретной компании. Все операции, 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¶
- При нарушении invariant остановить новые spending actions affected scope.
- Сохранить transaction/operation evidence и определить авторизованные commits.
- Сверить postings и external settlement, не удалять финансовую историю.
- Применить approved compensating postings для подтверждённых ошибок.
- Исправить locking/guarding и проверить две concurrent sessions, rollback и retries.
- Возобновить после 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.
Источники и версии¶
- PostgreSQL isolation — 17.
- PostgreSQL locking — 17.