← ~/blog

Why gRPC Deadlines Are Not Optional and Never Were

 /  systems  /  303 words

gRPC will happily let you make a call with no deadline, which means the default timeout is infinity, which means the default is a promise to wait forever for a service that might be having the worst day of its life. Most codebases I have audited take the default.

A call without a deadline holds resources on both sides while it waits. Multiply by a traffic spike and a slow dependency, and you get the classic failure shape: nothing is technically broken, everything is just waiting, thread pools drain, and the whole system settles into a tar pit where requests neither succeed nor fail. Failure is recoverable. Waiting forever is not, because nothing ever triggers recovery.

Two grpcurl calls to an inventory check. With a 2 second deadline the call fails with DeadlineExceeded; with a 10 second limit the same lookup succeeds after 6.4 seconds.

That capture is from the incident that converted me. With a 2 second deadline the inventory check failed fast and loud, deadline exceeded, impossible to ignore. When I raised the limit to 10 seconds it "worked," in 6.4 seconds, for a lookup that should take 50 milliseconds. The deadline was not causing a problem. It was the only thing in the stack telling the truth about one.

The part that makes gRPC deadlines genuinely good rather than just necessary: they propagate. Set a deadline at the edge and every downstream hop inherits the remaining budget automatically. When the frontend gives up at 2 seconds, the database query four hops deep gets cancelled too, instead of finishing an answer nobody will ever read. Cancellation flowing down the call graph is free garbage collection for doomed work.

Policy that has served us well: every call site sets a deadline, budgets set top down from the user facing SLO, and anything that needs more than a second gets asked, politely, whether it should be an async job instead. Infinity is not a timeout. It is the absence of a plan.