A fast database and a slow API do not contradict each other. Query time measures work, API latency measures waiting.
Queries come back in single-digit milliseconds and the request still takes a second. The time is not in the query. It sits in occupancy: how long each request holds a scarce resource while not actively using it.
The usual shape: a transaction opens early in the handler, and then non-database work happens inside it. A file write to object storage. A call to a verification provider. A document sent for signing. Serializing a large response before the commit.
Every query in that span is still fast. The connection is checked out for the whole span anyway.
So your effective concurrency is not the pool size. It is the pool size divided by how long a connection sits held but idle. Past that point nothing looks broken: the database keeps reporting healthy query times, because requests now queue for a connection before they ever reach a query. That wait shows up in no slow-query log you own.
How I approach it: the transaction is the shortest span in the handler. External I/O and file writes live outside it. Reads that can happen before the write boundary happen before it. In payment flows I think this stops being a style preference, since a hold that keeps a connection open across a provider call converts a partner's bad minute into your outage.
The part worth keeping: stop optimizing query time and start optimizing connection occupancy time. Two different numbers, and only one of them is visible by default.
I unpacked the rest of these patterns here (transaction boundaries, pool sizing, where the waiting actually hides): https://polycratia.com/c/18419e71
If you are profiling an API right now, check whether you measure time spent waiting for a connection separately from time spent executing the query.