polycratia

A three-second response is rarely three seconds of work. Usually it is a few milliseconds of work, waiting its turn.

The signature is easy to read once you know it: the endpoint's query time looks fine in isolation, CPU is unremarkable, and latency climbs with concurrency instead of with payload size. That is not a slow system. It is a serialized one.

In ledgering code the serialization point is almost always one hot row. Every request that touches an account's balance takes a row lock, and while holding it the handler does other work too: writes an audit entry, fans out a notification, computes a pro-rata split across multiple investors in a deal. Each step is fast by itself. Together they stretch the critical section, and under load every arriving request pays for all the ones queued ahead of it.

Two changes matter, in this order.

First, move everything out of the critical section that does not have to be in there, starting with anything outbound. A network call inside a lock turns someone else's latency into your throughput ceiling.

Second, stop locking a mutable total. Append immutable entries and derive the balance, and concurrent writers stop competing for the same row at all.

The habit worth keeping: measure lock wait as its own number, separate from query duration. Most dashboards report how long statements ran, not how long requests sat before they were allowed to run, which is exactly where the three seconds live.

I write more about how I design attribution and ledgering here: https://polycratia.com/c/f7588593

So it is worth knowing where your write path serializes first: one hot row, one queue, or one external call inside a transaction...

react

$ new-project --brief

or email hey@polycratia.com