polycratia

A catalog endpoint kept timing out on mobile, and the slow-query log for that window was empty.

Postgres was fine: the query came back fast, the plan was clean, the indexes were used. Whatever time I was losing was going somewhere after the rows were already in memory.

So I split the handler's wall time into segments: fetch, object construction, serialization, bytes written. The query turned out to be a rounding error. Most of the budget went into nested serializers expanding related objects row by row, formatting prices and delivery estimates per item, and then shipping a response big enough that a phone on a mobile connection spent longer downloading it than the server spent producing it.

No database metric was going to tell me that. The database measures what it was asked to do. It cannot measure what my code does with the answer, or what the client pays to receive it.

What changed afterwards: the response contract came first. Flat payloads for list views, heavy fields only on the detail endpoint, serialization treated as a cost with a budget instead of a free translation layer. Response size became a reviewable number, the same way query count is.

Now I treat an endpoint as guilty until its time is accounted for end to end, and not just up to the point where the database stops being involved.

I wrote this one up in more detail here: https://polycratia.com/c/2b24e4ff

If you want to know where your slowest endpoint actually spends its time, you need the whole picture: fetch, object graph, bytes on the wire.

react

$ new-project --brief

or email hey@polycratia.com