A Layer-by-Layer Guide to Diagnosing API Timeouts Without Guessing
Timeout incidents routinely waste hours as teams blame the network, the database, or the code without evidence. A repeatable, layer-by-layer checklist turns a multi-team argument into a 15-minute forensic exercise and prevents the common mistake of masking a slow database with bigger thread pools.
When an API times out, the instinct to restart or blindly expand pools often destroys the evidence and pushes pressure downstream. A more reliable approach follows the request path from the outside in, answering one question at each layer: did the request arrive, and how long did it stay?
The diagnostic sequence starts with a curl command to separate client-side from server-side latency. Nginx access logs enriched with upstream timing variables then reveal whether the connection to the application was refused, established slowly, or established quickly but followed by a long wait for response headers. Inside the JVM, three consecutive thread dumps pinpoint whether Tomcat threads are blocked waiting for a HikariCP connection, stuck on a socket read, contending for a lock, or genuinely CPU-busy.
At the connection pool layer, four numbers tell the story: active, idle, pending, and max. A pool at maximum with a growing pending queue confirms congestion, but the root cause is further down — slow SQL, oversized transactions, or remote calls inside transactions. MySQL's process list and Performance Schema queries then distinguish between long-running statements and sessions waiting on locks, guiding the final step of checking execution plans or identifying blocking transactions.
The diagnostic sequence is deliberately ordered by proximity to evidence: Nginx logs are always available, thread dumps require JVM access, and MySQL queries need database credentials, so starting from the edge avoids wasted context-switching.
Status code 499 is widely misunderstood as an Nginx problem when it often just means the client's timeout budget was smaller than the server's response time — a mismatch in timeout configuration across layers.
The advice to capture three thread dumps rather than one is underappreciated. A single dump can mislead by catching a thread in a transient state; repetition filters noise.
Connection pool exhaustion is a symptom, not a diagnosis. The article correctly frames it as a congestion indicator that forces the next question: what is holding the connections?
EXPLAIN ANALYZE runs the actual query, which makes it dangerous on production write paths or large tables — a caveat many guides omit.