polycratia

Most 3-second endpoints are not slow because of the code inside them. They are slow because of everything the response path quietly got made responsible for.

When I open a slow endpoint, the handler itself is rarely the cost. The cost sits in three places.

First, query count. One list endpoint, a nested serializer, and suddenly every row triggers its own lookup. The handler profile looks fine because no single query is slow: there are just hundreds of them. Fixing that is not optimization, it is making data access explicit. Select the relations you serialize, and don't let a serializer decide what to fetch.

Second, synchronous side work. Notifications, audit writes, a provider callback, a document render. Each one is defensible on its own, and none of them belong to the request. In the payment and compliance systems I have shipped, the audit write is the one people are most afraid to move: it feels like it has to happen before you answer. It doesn't. It has to happen durably. Those are different requirements, and conflating them is what puts an external dependency inside your p95.

Third, the response shape. An endpoint that returns everything a client might need pays for everything on every call. Shrinking the payload is usually a bigger win than caching it.

What changes when latency drops that far is the boundary of the request, not the speed of the work. Same work, different place. The queue absorbs what the user never needed to wait for.

And the honest part: the 3 seconds was often never measured as a wait at all. It was measured as handler time, which hides queue wait, connection pool contention, provider timeouts. You cannot fix what your instrumentation attributes to the wrong layer. Time the full path from the client's side first, then decide what to move.

The trade-off is real. Moving work out of the response path means the client gets an answer before the system is fully consistent, and you now owe the user some way to see that. Status fields, idempotent retries, something to poll. That is the actual engineering cost of 200ms, and I think it is worth saying up front rather than discovering it in support tickets.

I write up more of these production trade-offs on my blog: https://polycratia.com/c/b2b0cdbf

For those of you who have done this: what did your instrumentation misattribute the longest, queue wait, pool contention, or an external call hiding inside the handler?

react

$ new-project --brief

or email hey@polycratia.com