A three-second endpoint is usually not doing three seconds of work. It is waiting for a lock.
It hides well, because everything you instrument looks healthy. CPU flat, query plan fine, per-statement execution time in milliseconds: lock acquisition is not execution, and a lot of tooling reports only the latter. The time disappears between request received and statement started, and that is exactly where most traces carry no span.
What gives it away is the shape of the latency, not the size. Contention is bimodal: almost every request is fast, a minority takes seconds, and the slow ones cluster by clock time rather than by traffic. When your p99 spikes line up with a settlement or reconciliation run instead of with peak load, that is not a performance problem. It is two writers meeting on the same row.
In ledgering work this is the default failure mode. A mutable balance row is a global serialization point: every credit, debit and attribution update competes for it, and one long-running job holding it blocks reads that only wanted a number. The fixes are structural rather than clever. Keep transactions short enough that no business logic or outbound call runs inside them, acquire locks in a consistent order, and prefer append-only entries with derived balances over one hot row everyone has to update.
Slow code you can profile. Contention you have to design away.
I write up more of these production patterns here: https://polycratia.com/c/67624695
Balances, inventory, counters: you probably already know which one surfaces first in your system...