Load test results

Runs of the Control Center's load test (Load test in the sidebar) against the live server at propustech.tech, October 2026. Each run links to its raw report.

Setup

Server4-core, 16 GB VPS shared with other services
Propus limits3 cores, 4 GB memory (systemd CPUQuota=300%, MemoryMax=4G)
GuardNew users pause above 80% host CPU or memory
BuildSingle binary, all engines in one process
DataA fresh database per run; the catalog, customers and coupons written by an AI model for a made-up business

Workload

An online store and its back office on every engine at once:

  • CoreStore: 18 tables (catalog, warehouses, stock and stock movements, carts, orders and lines, invoices, payments, coupons, loyalty, double-entry ledger, notifications) with row-level security per region. Checkout, ship, deliver, cancel and return are interactive transactions.
  • FlashCache: product pages, carts, rate limits, stock locks.
  • PulseStream: a relay reads the change feed into an orders stream; a notifier group writes notifications and fans out to email, SMS and push streams; senders retry three times, then dead-letter; an analytics consumer rebuilds order counts.
  • ObjectCore: product images and invoice documents.
  • TrackSense: product search kept in step with the catalog.

Users are shoppers (browse, search, cart, checkout, resubmit, cancel, return), staff (ship, deliver, receive stock) and managers (price changes, reports, trial balance), mixed 70 / 20 / 10.

Rules checked

RuleHolds when
no-oversellStock never goes below zero and reserved never exceeds on hand, even when many shoppers race for the last units
reserved-matches-open-ordersReserved stock equals the quantities of paid orders not yet shipped
stock-matches-movementsEvery warehouse's stock equals the sum of its stock movements
order-moneyEach order's total equals its lines plus tax less discount, and equals its invoice and payment
ledger-balancesDebits equal credits, overall and for every posting
couponsEach coupon's use count equals the orders using it and never passes its limit
loyaltyEach customer's points equal the points of their orders still standing
idempotencyA resubmitted order never creates a second order
tenant-isolationA region's users never see another region's rows
cache-consistencyA cached value reads back the same; a lock is never held twice; documents read back intact
events-completeEvery order change reaches the stream once and makes exactly one notification
events-orderedEvents for one order arrive in the order they happened
deliveries-settleEvery notification ends delivered or dead-lettered
analytics-matchesOrder counts rebuilt from the events equal the counts in SQL

Results

Every run passed every rule, with no errors.

Run (UTC)UsersTransactionsSecondsAvg / peak tx/sOrdersRule checksFailedEvents published / delivered / deadEnded
2026-10-22 04:243,00010,10441024.6 / 44.03,01219,06609,639 / 9,638 / 1Reached its 10,000 target
2026-10-21 22:3610,0004,45613333.5 / 50.51,2889,50804,016 / 4,016 / 0Stopped, then checked
2026-10-21 22:335,0008,11126830.3 / 52.52,43415,36007,737 / 7,737 / 0Stopped, then checked
2026-10-21 21:325,0008,63331527.4 / 62.52,41116,44008,097 / 8,097 / 0Stopped, then checked

The one dead letter is a sender giving up after three simulated failures, as designed.

The 3,000-user run

Business3,012 orders · 2,194 shipped · 2,174 delivered · 790 cancelled · 1,469 returned · 340 price changes · 125 stock receipts
Refused correctly633 out of stock · 117 coupon at its limit · 1,615 resubmits blocked
Busy writes0 (retried writes all went through)
Cache5,963 hits, 3,673 misses
Events101,307 changes read · 9,639 published · 9,638 delivered · 308 retries · lag 0 at the end
Host CPU64% at most
OperationCountp50 msp95 msp99 ms
search4,8672.08.917.3
cart4,9322.65.59.1
my-orders3,1922.25.98.9
browse9,6365.058.781.8
reports43059.5270.6466.5
trial-balance421197.9453.8576.1
checkout6,4224,208.65,877.16,931.8
deliver2,8904,192.35,759.97,014.3
return1,4724,032.65,667.26,654.5

What it found

Fixed before these runs:

  • random() was missing (42883); added.
  • Joins were nested loops (5.6 s on the report queries); CoreStore now plans hash joins for equality joins, checked against PostgreSQL.
  • Typed literals (DATE '2026-10-21', NUMERIC '1.5') were refused; now casts.
  • Under thousands of users the server ran out of threads; thread limits raised, spawn failures handled, a run that panics is contained.
  • Writes waiting on the writer for 10 s failed with 55P03; they are now retried, then counted as busy.
  • The CPU guard read nothing when systemd hid /proc; the installer sets ProcSubset=all and a run refuses to start without the readings.
  • Stop waited for in-flight users; it is now immediate, and Finish & check runs the final checks.

Limits shown

  • Writes go through one writer. Reads stay in single-digit milliseconds, but write transactions wait their turn: about 25–60 business transactions a second, falling as users rise; checkout p50 is about 4 s at 3,000 users.
  • Notifications lag under saturation. Delivery p95 was 5.2 minutes at 3,000 users (12 s in the first 5,000-user run). Nothing is lost; it catches up.

Next engine work: server-side transactions in one call (shorter writer holds), group commit, then row-level write concurrency.