Flo

Performance & Scalability

What LedgerFlow delivers under real sustained load — and why.

The numbers on this page come from a real 1-hour JMeter load test run on 28 March 2025 against a deliberately constrained environment: a VMware virtual machine with 4 CPU cores and 8 GB of Docker memory, running on a consumer-grade mini-PC. No dedicated database server. No hardware RAID. No read replicas.

The purpose is to establish a credible, reproducible baseline that undersells production capacity — so that real deployments comfortably exceed these numbers.

← Architecture Overview

Test Environment — Real 1-Hour Sustained Load Test

Test date: 28 March 2025 · Duration: 60 minutes continuous · Tool: Apache JMeter on MacBook M1 (32 GB RAM) over 1 Gbps

Layer Specification
Host machine Mini PC · Intel i5-12450H (2 GHz) · 64 GB RAM · 2× 1 TB SSD · Windows Pro
Virtualisation VMware Workstation 17 Pro · 4 CPU cores · 16 GB RAM · 64 GB virtual SSD
Container runtime Docker · WSL 2 · 8 GB memory limit · 64 GB SSD
Concurrent threads 10 JMeter threads (2 thread groups × 5 threads each)
Test payloads Multi-currency transactions with parent account hierarchies
Services under test Full platform stack — UI, API, database, async workers, scheduler

This is a small configuration — it excludes failover, read replicas, and multiple API nodes. Production deployments with dedicated hardware will significantly exceed these results.

Throughput Over 60 Minutes — Sustained Load Test

The chart shows 5-minute throughput bars across the full 60-minute test. After a brief ramp-up (first ~15 minutes) the system reached a stable steady state at 466.9 transactions per second and maintained it without degradation, errors, or manual intervention.

All 1,680,964 transactions completed successfully — zero errors in 60 minutes.

The 466.9 TPS figure represents complete double-entry transactions: each one writes a transaction header, multiple debit/credit detail rows (average ~3 per transaction), a running-balance record, and an audit entry. The total of 5,012,190 DR/CR detail rows and 5,667,710 total database records written reflects this multiplier.

Throughput & Volume

Metric Result
Average TPS 466.9 transactions / second
Total transactions (60 min) 1,680,964
DR/CR detail rows written 5,012,190
Total database records written 5,667,710
Error rate 0% (zero errors)
Storage per transaction 366 bytes (incl. indices)
Storage per detail row 251 bytes (incl. indices)

466.9 TPS on a constrained VM with 10 concurrent clients demonstrates that the database-centric architecture does not create a bottleneck — business logic, queuing, and scheduling all run inside the database without an external broker increasing latency.

Latency Profile

Metric Result
Core processing latency 11 ms per transaction
End-to-end latency 20 ms per transaction
Latency delta (network + API layer) ~9 ms

Core processing latency is the time spent inside the database executing the stored procedure (validation, rule evaluation, record inserts, balance update, audit trigger). At 11 ms this includes: balance validation, account limit checks, optional rule-engine traversal, partitioned record inserts, real-time balance update, and audit trigger execution.

End-to-end latency adds NGINX routing, JWT verification, and connection acquisition overhead. At 20 ms this is well within the sub-100 ms threshold required for interactive use and payment gateway callback flows.

Storage & Growth Projections

Based on the load test write rates (2 GB/hour) scaled to continuous operation:

Period Transactions Storage
Per hour ~1.68M ~2 GB
Per day ~40.3M ~48 GB
Per month ~487M ~1.5 TB
Per year ~5.8B ~18 TB

These figures assume continuous full load at 466.9 TPS — a realistic worst-case for high-volume PSPs. Real deployments have daily peaks and troughs; actual storage will typically be 20–40% of the continuous-load projection.

Partition strategy: Range partitions distribute writes and enable old partitions to be archived independently. Partition management is fully automated.

Write Path Performance Design

Three design decisions deliver sub-20ms end-to-end write latency at sustained load:

1. Hot/Cold table split A dedicated hot store holds only live account balances and is updated in-place with minimal overhead — enabling fast balance reads during transaction validation. Full account history and configuration live in the cold store and are rarely touched during writes.

2. Time-embedded ID partitioning Transaction IDs embed a timestamp — partitioning by ID range naturally groups records by time, enabling partition pruning on date queries without a separate date column. Partitions distribute write I/O and can be archived independently.

3. Dual API pools Transaction and maker-checker requests are automatically routed to a dedicated high-throughput pool. General API queries (account lookups, reporting) use a separate pool. This prevents long-running queries from delaying in-flight transactions.

Connection Pool Architecture

Service Connections Notes
SQLPage Web UI 20 Direct SQL, session-scoped
PostgREST Default 10 General API queries
PostgREST Fast 6 Transaction writes
HTTP Client (Go) 1 persistent Queue polling
SMTP Client (Go) 1 persistent Queue polling
WebSocket Server (Go) 1 persistent Queue polling
DB Processor (Go) 1 persistent Queue polling
Total active ~40 of 200 max

During the 1-hour load test, active connections remained well below the ceiling, leaving headroom for monitoring queries, scheduled jobs, and administrative access.

What Runs Inside Every Transaction

When a transaction is submitted via the API, the following happens inside a single database transaction before the caller receives a response:

  1. Input validation — account existence, active status, currency match
  2. Account limit checks — per-transaction amount, velocity (hourly/daily/weekly/monthly), balance ceiling/floor
  3. DR/CR sign normalisation — by account type (Asset: DR+, Liability: DR−, etc.)
  4. Rule engine evaluation — up to 6 rule types may generate secondary transactions automatically
  5. Transaction header stored — unique ID assigned, partitioned by time range
  6. Debit/Credit detail rows stored — one row per posting, partitioned
  7. Account balances updated in real time — in the hot store, rules and thresholds enforced
  8. Running balance snapshot captured — cumulative balance record for each affected account
  9. Fraud detection — if enabled on account, anomaly score computed and flagged
  10. Audit trail recorded — every change captured with user, timestamp, and before/after state

All 10 steps complete in 11 ms on average on the constrained VM.

Observability & Monitoring

The observability stack runs alongside the platform with no additional configuration:

Prometheus scrapes all platform services every 15 seconds: database connection counts and query performance, both API pools (request rates, error rates, upstream latency), NGINX edge metrics, and all Go microservices (queue depth, processing time, retry rates).

Tempo receives OpenTelemetry spans from NGINX and correlates them with database query timings for end-to-end distributed traces. Retention: 7 days.

Loki aggregates structured logs from all services. Grafana allows log-to-trace correlation.

Pyroscope continuously profiles microservice CPU and memory usage.

Pre-provisioned Grafana dashboards cover: write latency, queue depth, API latency percentiles, and business metrics (transactions per hour, reconciliation match rates).

Scaling to Production

The constrained test environment is intentional. When deployed on dedicated hardware the same architecture scales significantly:

Change Expected Impact
Dedicated database server (8 core, 16 GB, NVMe SSD) 3–5× throughput increase
Add API service replicas behind NGINX Linear API request scaling
Add async worker replicas Proportional queue throughput
Add read replica Offload reporting queries from write path
Increase transaction pool capacity Higher concurrent transaction TPS

The database-centric architecture means there is no external broker to scale — message queuing, job scheduling, and hot-data caching all live inside the database. This reduces the number of moving parts and ensures ACID guarantees across all operations, including queue message delivery.