SERIALIZABLE isolation
All posting operations execute at SERIALIZABLE isolation, not the default read-committed. Conflicts are retried with exponential backoff; the ledger_serialization_retries_total metric exposes contention.
DoubleX is a production-grade double-entry ledger service with strong
consistency guarantees: SERIALIZABLE isolation at the database level,
idempotent postings with advisory-lock de-duplication, end-of-day close with balance
snapshots, FX settlement via the clearing-account method, reconciliation CLI with CSV
import and tolerance matching, and first-class observability through Prometheus,
OpenTelemetry and a Grafana SLO dashboard.
tx-0c47a2idempotency_key
The buttons below call the real DoubleX API deployed at
stelioszach.com/doublex-ledger/live/api/transfers/. Post a balanced
journal once, then click Replay — the service returns the
same journal_id with status=duplicate and the
account balance does not move. That's idempotency enforced at the
DB layer, not by application discipline.
{}
Every guarantee is enforced where it's hardest to break — at the database layer. The service code is thin on top: it reads, validates, posts under a transaction, and surfaces the solver's view. The invariants don't rely on application discipline.
All posting operations execute at SERIALIZABLE isolation, not the default read-committed. Conflicts are retried with exponential backoff; the ledger_serialization_retries_total metric exposes contention.
Every transfer carries an idempotency_key. A retry of the same payload returns the original posting — not a duplicate. Implemented with advisory locks plus an upsert sentinel; safe across concurrent clients.
A single command closes the day: freezes balances to snapshot rows, records the close timestamp, and installs a backdating guard that rejects any posting earlier than the latest closed day. Re-runnable.
Cross-currency transfers post two balanced journals joined through a per-currency clearing account. Rates are upsertable and normalized (pair inversion handled once); rounding policy is explicit in decimal context.
Bulk CSV import, exact ref match first, then amount within ±0.01 tolerance. Outputs CSV/JSON/HTML artifacts and inserts ReconException rows; served via /api/reports/recon.
Prometheus counters for ledger_tx_total, latency histograms for post_journal and get_balance, OTEL spans on every business path, and a pre-provisioned Grafana Ledger SLO dashboard.
The schema enforces the invariants — balanced journals, non-negative balances where required, currency match on legs — so the application can be stateless. Alembic owns migrations; SQLAlchemy 2.0 models generate them. OTEL and Prometheus wrap every request and every span.
── ingress ────────────────────────────────────────────────────────── client ─── POST /api/transfers ──▶ FastAPI ──▶ Pydantic v2 │ ▼ ── business logic ─────────────────────────────────────────────────── ledger.domain.double_entry + idempotency (advisory lock) + fx (two-journal method) + eod (backdating guard) │ ▼ ── persistence ────────────────────────────────────────────────────── SQLAlchemy 2.0 ──▶ PostgreSQL SERIALIZABLE isolation DB triggers: balance = 0 currency match non-negative │ ▼ ── observability ──────────────────────────────────────────────────── OTEL spans + Prometheus /metrics + Loguru (JSON) │ │ ▼ ▼ OTLP collector Prometheus scrape │ │ └──────────┬─────────────┘ ▼ Grafana · Ledger SLO dashboard (TPS · p95 post_journal · SERIALIZABLE retries · recon mismatches)
Accounts, transfers, journals, reports. REST + OpenAPI, Pydantic-validated. The entire production surface fits in four prefixes; everything else — EoD, reconciliation imports — happens via Typer CLI or background workers.
Coverage as a floor, latency as a ceiling, retries as contention telemetry, reconciliation mismatches as correctness. Everything else is a derivative.
ledger_serialization_retries_total.ReconException rows.Every layer is independently testable and observable. The domain is pure Python, the persistence layer owns the invariants, and the service tier is thin glue.
| Layer | Tech | Role |
|---|---|---|
| Domain | Pure Python · decimal | double_entry · eod · fx · idempotency · locking. Property-tested with Hypothesis. |
| API | FastAPI · Pydantic v2 | /api/accounts · /api/transfers · /api/journals · /api/reports. OpenAPI 3.1 auto-generated. |
| Persistence | SQLAlchemy 2.0 · Postgres | Alembic migrations, SERIALIZABLE isolation, DB triggers enforcing balance + currency + sign. |
| Observability | Prometheus · OTEL · Grafana | Counters, histograms, OTLP trace export; pre-provisioned Ledger SLO dashboard. |
| Tooling | Typer CLI · Locust bench | Recon import / export CLI, Locust load-test harness, CI on GitHub Actions. |
| Runbook | Docker Compose · Alembic | MIGRATE_ON_START=true auto-runs Alembic at boot; JSON logs via Loguru when LOG_FORMAT=json. |