Architecture Review: Financial Ledger Platform
A strict consistency financial architecture for double-entry accounting, payment processing, and audit trail management where every debit must have a corresponding credit, every state transition must be ordered and durable, and the complete history must be reconstructable without gaps. Event sourcing provides an append-only audit log; the outbox pattern guarantees at-least-once downstream event delivery without distributed two-phase commit (idempotent consumers deduplicate on event ID for effectively-once processing); PostgreSQL provides ACID guarantees for ledger entry atomicity.
Evidence Confidence
Moderate
strong
Executive Summary
Financial Ledger Platform: moderate operational readiness (81% evidence confidence). 6 architectural strengths identified, 4 operational risks to manage. Primary concern: Lock Contention. Requires Advanced operational maturity.
Readiness Rationale
Overall moderate readiness across 8 dimensions. Limited: team maturity. Strong: migration, observability, failure recovery.
Key Concerns
- !Lock Contention
- !Split-Brain
Key Strengths
- +The outbox pattern eliminates split-brain between a database write and a message broker publish by writing both the domain record…
- +Financial transaction workloads benefit from event sourcing because the event log provides an immutable audit trail, enables…
- +Write-heavy transactional workloads that emit downstream events (order placed, payment captured) benefit from the outbox pattern…
- +PostgreSQL serves as a capable event store for moderate event volumes, leveraging JSONB payloads, UNIQUE constraints for…
8
Assessments
4
Tradeoffs
6
Sections
11
Recommendations
Readiness Assessments
8Architectural Tradeoffs
4Recommendations
11Monitor: Lock Contention
risk_monitoringConcurrent writers to the same rows serialize behind each other's row locks, so latency is set not by the work a transaction does but by how long it waits for the writers ahead of it. On a hot row the queue depth, and therefore the tail latency, grows with concurrency while throughput flattens. Blocked writers hold connections open, so a single contended row can drain the connection pool as a secondary failure.
Affects 1 node. (Write-Heavy Transactional)
Monitor: Split-Brain
risk_monitoringA failover mechanism promotes a new leader without confirming the old one has stopped, so two nodes simultaneously believe they hold the primary role and both accept writes. The two histories diverge, and when the partition that triggered the failover heals, one set of committed transactions must be discarded.
Affects 1 node. (Two-Phase Commit (2PC)). 1 mitigation identified
Implement: Monitor generic risk probe signals
observabilitySeed 'Lock Contention Risk Probe' identifies 2 metrics relevant to lock_contention.
Metrics to instrument: error_rate, p95_latency_ms
Mutable account balance table with no event history → Event sourced ledger with append-only events and projected balance view
migration_planningTrigger: Audit requirement to reconstruct account state at any historical point in time; compliance requirement for complete transaction history; inability to explain why a balance is what it is from current state alone. Migrate from 'Mutable account balance table with no event history' to 'Event sourced ledger with append-only events and projected balance view'. Bootstrap historical state as a single "initial_balance" event per account. Run the event-sourced model in parallel with the mutable model for 30 days before decommissioning the mutable path.
Historical event reconstruction is only possible from the migration cutover date: pre-migration history must be bootstrapped as initial balance events; Projected balance views must be kept consistent with the event log; any discrepancy indicates a bug that must be caught in testing
Synchronous Kafka publish in transaction (dual-write pattern) → Outbox pattern with CDC relay to Kafka
migration_planningTrigger: Kafka publish failures causing financial transaction rollbacks; or Kafka publish succeeding but transaction rolling back, causing phantom events downstream. Migrate from 'Synchronous Kafka publish in transaction (dual-write pattern)' to 'Outbox pattern with CDC relay to Kafka'. The outbox pattern is non-negotiable for financial systems. The synchronous dual-write is an anti-pattern that creates phantom events on rollback.
Outbox relay introduces a delivery lag (typically < 5s); downstream consumers must handle this eventual delivery, not assume synchronous availability; Relay stall must trigger an immediate operational alert: unmonitored stall is a consistency incident
Prepare runbook for: Burst Traffic Cold Cache Stampede
simulation_preparednessSimulation demonstrates critical degradation of redis, postgresql
Without a runbook, recovery from this failure mode will be ad-hoc
Prepare runbook for: Connection Pool Exhaustion with Horizontal User Scale
simulation_preparednessSimulation demonstrates critical degradation of postgresql
Without a runbook, recovery from this failure mode will be ad-hoc
Plan evolution: OLTP Analytics Queries → OLTP + OLAP Separation
evolution_planningEvolution from Unified OLTP + Analytics on PostgreSQL → Separated OLTP (PostgreSQL) + OLAP (ClickHouse/Snowflake)
Migration complexity: medium. Rollback: always.
Plan evolution: PostgreSQL → Partitioned PostgreSQL
evolution_planningEvolution from Single-Node PostgreSQL → Partitioned PostgreSQL
Migration complexity: high. Rollback: rarely.
Monitor threshold: Tier 1: Hot Account Lock Contention
scaling_monitoringSignal: pg_locks shows contended rows on accounts table; write p99 > 50ms; deadlock errors in application logs; pg_stat_activity showing many transactions waiting for RowExclusiveLock on the same account rows
Bottleneck: Concurrent debit/credit transactions competing for the same account row versions. Evolution: Implement optimistic locking with version column and retry; or queue concurrent updates for the same account entity through an account-scoped serialization queue at the application layer; or partition the accounts table by account range
Monitor threshold: Tier 2: Synchronous Replication Write Latency
scaling_monitoringSignal: Write p99 > 100ms with synchronous_commit = remote_apply; replica WAL apply lag visible in pg_stat_replication; network jitter between primary and replica causing write latency spikes correlating with replication ACK delays
Bottleneck: Synchronous replication write-ahead wait amplifying network latency for every committed transaction. Evolution: Co-locate primary and replica in the same availability zone for lowest replication RTT; tune wal_sender_timeout and recovery_min_apply_delay; evaluate whether synchronous_commit = on (durable to primary WAL only) is acceptable for your regulatory risk model
Scaling Pressure Signals
8pg_locks shows contended rows on accounts table; write p99 > 50ms; deadlock errors in application logs; pg_stat_activity showing many transactions waiting for RowExclusiveLock on the same account rows
Threshold
Tier 1: Hot Account Lock Contention
Likely Bottleneck
Concurrent debit/credit transactions competing for the same account row versions
Recommended Evolution
Implement optimistic locking with version column and retry; or queue concurrent updates for the same account entity through an account-scoped serialization queue at the application layer; or partition the accounts table by account range
Write p99 > 100ms with synchronous_commit = remote_apply; replica WAL apply lag visible in pg_stat_replication; network jitter between primary and replica causing write latency spikes correlating with replication ACK delays
Threshold
Tier 2: Synchronous Replication Write Latency
Likely Bottleneck
Synchronous replication write-ahead wait amplifying network latency for every committed transaction
Recommended Evolution
Co-locate primary and replica in the same availability zone for lowest replication RTT; tune wal_sender_timeout and recovery_min_apply_delay; evaluate whether synchronous_commit = on (durable to primary WAL only) is acceptable for your regulatory risk model
PostgreSQL WAL volume > 500MB/minute sustained; event sourcing table growing faster than VACUUM can reclaim; wal_buffers flushing > 2x per second; I/O utilization on WAL volume > 80%
Threshold
Tier 3: Event Log Volume and WAL Saturation
Likely Bottleneck
Event sourcing append rate combined with balance projection writes saturating WAL throughput
Recommended Evolution
Move WAL to a dedicated NVMe volume; tune checkpoint_completion_target to 0.9; partition the events table by time range (monthly partitions) to bound per-partition VACUUM scope; evaluate whether the balance projection can be computed lazily (on read) rather than maintained eagerly (on write)
PostgreSQL primary sustaining > 3000 TPS on financial transactions; write p99 > 200ms despite I/O and pool optimization; audit event table exceeding 1 billion rows
Threshold
Tier 4: Single-Primary Throughput Ceiling
Likely Bottleneck
Single PostgreSQL primary write throughput ceiling for ACID transactional workloads
Recommended Evolution
Evaluate domain partitioning by currency, region, or account range across multiple PostgreSQL primaries with saga-based cross-shard coordination; or evaluate CockroachDB for distributed ACID writes with global consistency guarantees
pg_locks shows contended rows on accounts table; write p99 > 50ms; deadlock errors in application logs; pg_stat_activity showing many transactions waiting for RowExclusiveLock on the same account rows
Threshold
Escalation trigger: Concurrent debit/credit transactions competing for the same account row versions
Likely Bottleneck
Tier 1: Hot Account Lock Contention
Recommended Evolution
Monitor: error_rate, p95_latency_ms
Write p99 > 100ms with synchronous_commit = remote_apply; replica WAL apply lag visible in pg_stat_replication; network jitter between primary and replica causing write latency spikes correlating with replication ACK delays
Threshold
Escalation trigger: Synchronous replication write-ahead wait amplifying network latency for every committed transaction
Likely Bottleneck
Tier 2: Synchronous Replication Write Latency
Recommended Evolution
Monitor: error_rate, p95_latency_ms
PostgreSQL WAL volume > 500MB/minute sustained; event sourcing table growing faster than VACUUM can reclaim; wal_buffers flushing > 2x per second; I/O utilization on WAL volume > 80%
Threshold
Escalation trigger: Event sourcing append rate combined with balance projection writes saturating WAL throughput
Likely Bottleneck
Tier 3: Event Log Volume and WAL Saturation
Recommended Evolution
Monitor: error_rate, p95_latency_ms
PostgreSQL primary sustaining > 3000 TPS on financial transactions; write p99 > 200ms despite I/O and pool optimization; audit event table exceeding 1 billion rows
Threshold
Escalation trigger: Single PostgreSQL primary write throughput ceiling for ACID transactional workloads
Likely Bottleneck
Tier 4: Single-Primary Throughput Ceiling
Recommended Evolution
Monitor: error_rate, p95_latency_ms
Migration Readiness
12Migration Stages
3Mutable account balance table with no event history → Event sourced ledger with append-only events and projected balance view
infoMigration trigger: Audit requirement to reconstruct account state at any historical point in time; compliance requirement for complete transaction history; inability to explain why a balance is what it is from current state alone
Synchronous Kafka publish in transaction (dual-write pattern) → Outbox pattern with CDC relay to Kafka
infoMigration trigger: Kafka publish failures causing financial transaction rollbacks; or Kafka publish succeeding but transaction rolling back, causing phantom events downstream
Single PostgreSQL primary serving all reads and writes → CQRS with separate read model and write model
infoMigration trigger: Financial dashboard query p99 > 500ms causing dashboard-driven I/O competing with write transactions; reporting queries running against the primary during month-end close causing write latency spikes
Risks
9Historical event reconstruction is only possible from the mi
warningHistorical event reconstruction is only possible from the migration cutover date: pre-migration history must be bootstrapped as initial balance events
Projected balance views must be kept consistent with the eve
warningProjected balance views must be kept consistent with the event log; any discrepancy indicates a bug that must be caught in testing
Outbox relay introduces a delivery lag (typically < 5s); dow
warningOutbox relay introduces a delivery lag (typically < 5s); downstream consumers must handle this eventual delivery, not assume synchronous availability
Relay stall must trigger an immediate operational alert: unm
warningRelay stall must trigger an immediate operational alert: unmonitored stall is a consistency incident
Read model is eventually consistent: dashboards may show sli
warningRead model is eventually consistent: dashboards may show slightly stale balances during high write periods
Read model must be reconciled with write model periodically
warningRead model must be reconciled with write model periodically to detect projection drift
Cross-service workflows that previously used database transa
criticalCross-service workflows that previously used database transactions now require Saga orchestration. Mitigation: Design idempotent event handlers; implement compensating transactions for every multi-step workflow; test failure injection in staging
↗ modular-monolith-to-event-driven
Consumer lag silently accumulates: a lagging consumer is not
criticalConsumer lag silently accumulates: a lagging consumer is not a failed consumer. Mitigation: Alert on consumer lag rate-of-change, not absolute depth; implement dead letter queues with alerting
↗ modular-monolith-to-event-driven
Missing partition for current time window causes all INSERTs
criticalMissing partition for current time window causes all INSERTs to fail with 'no partition of relation found'. Mitigation: Create partitions 7-30 days in advance; alert when next partition does not exist before its time window opens
↗ postgresql-to-partitioned
Review Sections
6Referenced Intelligence