This benchmark uses the load testing suite in the Blnk repository, run unmodified. The commands are below, so you can reproduce them against your own deployment.
Setup
We ran these tests on a dedicated VPS running Blnk Core0.15.4.
The server and worker shared one 4 vCPU host; PostgreSQL 16 and Valkey 8 were managed; and k6 ran from a separate 4 vCPU machine.
On DigitalOcean, this ledger stack is $230.10/mo at list prices. That figure does not include the load generator.
This is the Blnk Core configuration used. Every shape on this page ran against it.
What was measured
Four traffic shapes, from least to most contended.
The load test posts every request with
inflight: true and skip_queue: false, the suite defaults. The API queues the transaction and returns once it is recorded. A worker then processes it and leaves it inflight. As a result, each shape was measured three times:
- Accepted throughput. How fast the API takes requests while p95 stays under 300ms.
- Applied throughput. How fast workers process those queued transactions onto balances.
- Reliability. Of those accepted requests, how many were processed, and how many got dropped or lost.
Throughput is reported in transactions per minute (TPM). The load test takes its input rates per second, so a
RATE of 200 offers 12,000 TPM.Accepted throughput
To measure how many transactions the API could accept, we increased the request rate in 30-second stages, resetting the ledger between each run. This table shows the highest throughput where p95 latency stayed under 300ms.
Contention does not slow down acceptance. The four numbers sit within 6 percent of each other, around 260,000 TPM, or roughly 4,300 requests per second. Accepting a transaction writes a queued record and returns. A worker then picks it up, processes it, and applies it to the balances.
Every request in every step returned
201, including the steps past the latency threshold. Nothing failed at any rate, in any shape.
Applied throughput
To measure how quickly workers processed queued transactions, we ran each workload for 30 seconds at its accepted ceiling, then measured how fast the backlog cleared.
Result: The API can accept transactions much faster than workers can apply them. After a 30-second burst at the accepted ceiling, the queue took 12 minutes to clear without contention and 37 – 45 minutes when traffic concentrated on a hot balance.
Contention reduced applied throughput by roughly 3×. Without contention, workers applied about 9,600 transactions per minute. When many transactions touched the same balance, that fell to between 2,830 and 3,500 TPM.
The reason for this gap is serialization. Transactions touching different balances can be applied in parallel. Transactions touching the same balance must wait on the same balance lock, so adding more workers provides less benefit.
The type of contention made comparatively little difference. Hot source, hot destination, and hot pair workloads all fell within the same 2,830 to 3,500 TPM range. Hot source was the slowest, about 19% below hot destination.
These applied rates are from the configuration above. You can raise them by tuning coalescing, hot-lane routing, and worker concurrency where they apply.
Reliability under contention
To verify that accepted transactions were eventually applied to a balance, we ran each workload for 30 seconds at its measured TPM ceiling, then allowed the worker queue to drain completely. The table compares the number of transactions accepted by the API with the number ultimately processed by the ledger. Any accepted transaction that was never applied would count as lost.
Across more than 500,000 transactions, every accepted transaction was eventually processed and applied. No accepted work was dropped, and the queue returned to zero after every run.
Contention may delay when a transaction reaches a balance, but it does not cause accepted transactions to be lost.
Conclusion
The practical capacity of a Blnk deployment depends heavily on traffic shape. These results are from a single-server setup: the API and workers shared one 4 vCPU host. On this 4 vCPU production setup, at $230.10/mo, Blnk sustained 9,600 applied transactions per minute when traffic was spread across balances. Sustained over 30 days, that is about 415 million transactions per month. Under heavy contention, where many transactions compete for the same balance, the same setup can still reliably run 2,830 to 3,500 TPM, or about 122 million to 151 million transactions per month.Note: Horizontal scaling and the right ledger architecture can increase accepted throughput, applied throughput, and monthly capacity across the board.
Reproducing these tests
Run these from the repository root against your own deployment.1
Run a 30-second load test
Run one workload for 30 seconds at the rate you want to test. Replace the URL with your Blnk server.
Start each run with an empty queue. If transactions from the previous run are still waiting to be applied, they may affect the next result.
2
Repeat for each workload shape
Run the same 30-second test for the remaining shapes by changing the scenario flags.The suite’s
baseline scenario uses a fixed number of virtual users, so it cannot maintain a specific arrival rate. To test uncontended traffic at a known rate, use contention with HOT_RPS=1.HOT_RPS and RANDOM_RPS cannot be set to 0.3
Wait for all accepted transactions to be applied
After the load test stops, run the following query repeatedly:When the count stops increasing across two consecutive checks, the workers have finished processing the run.To get applied TPM, pick two readings while the count is still rising. Record the processed count and the clock time for each, then divide the extra processed rows by the minutes between those readings:For example, if
processed goes from 12,000 to 21,600 in 1 minute, applied TPM is 9,600.4
Check for lost transactions
Compare the final
applied count with the number of transactions accepted during the k6 run.If the two numbers match, every accepted transaction was eventually applied. If applied is lower, some accepted transactions did not reach the ledger.Open the queue monitoring dashboard at http://localhost:5004/monitoring and look for pending, failed, or discarded work to see what happened.