polycratia

Every evening the endpoint got slower, and the database graphs stayed flat.

Query time barely registered. The handler was nothing: pull a catalog page, serialize it, return. Still p95 climbed, and it climbed in a pattern that matched nothing I could point at. Deploys didn't explain it, and neither did traffic volume or one heavy tenant.

What it did match was client network quality.

I was serving a mobile app straight out of a synchronous worker pool. A worker is not done when it hands back the response object. It is done when the last byte reaches the socket. Catalog pages are fat. A phone on a weak connection took seconds to drain one, and for every one of those seconds a worker sat pinned there, doing no work, still holding a database connection it had stopped needing.

So slow clients quietly turned into server capacity. Get enough of them at once and the pool saturates, and then everything slows down, including endpoints that touch no database at all. It read like a database incident. It was not one.

What I do differently now: a buffering reverse proxy in front of any synchronous pool, so the proxy soaks up the response and frees the worker right away. Release the database connection before serialization, not after. And I watch time-to-first-byte apart from time-to-last-byte: the first one describes your code, the second describes the networks your users sit on.

I write these up at more length here: https://polycratia.com/c/a9a63986

Your API is probably holding a worker somewhere after the actual work is done...

react

$ new-project --brief

or email hey@polycratia.com