Skip to content
About
Main content

Request/response and RPC

Where does a synchronous call belong in a data platform?

RPC (remote procedure call) is calling a function on another machine and waiting for the answer. HTTP calls to a REST service and gRPC calls are .

RPC has zero decoupling on all three dimensions. The caller blocks (or holds a continuation, meaning code that resumes later), requires the callee to be up right now, and must know the callee's address.

In exchange it gives you the one thing no asynchronous transport can: the result, and an error attributable to this specific call.

What synchronous calls buy, honestly

  • Backpressure for free. The caller is blocked, so it cannot run faster than the callee. This is not a mechanism you build; it is a consequence of the shape. It is truly under-appreciated: the most reliable flow control is a blocked thread.
  • Errors are immediate and attributable. No , no correlation ID, no "which of the 40 records in this batch failed?"
  • No extra infrastructure. No to run, size, upgrade or lose.
  • Read-your-writes. After the call the caller knows the new state.

What it costs

  • Availability multiplies. A chain of five services that are each 99.9% available is 0.999⁵ ≈ 0.995, which is 99.5%: about 3.6 hours of downtime per month instead of 43 minutes. Every hop is a mandatory dependency.
  • Latency adds up, and tail latency compounds. If each hop's p99 is independent, a five-hop chain's p99 is much worse than five times the median. makes it worse: waiting for 10 parallel calls means waiting for the slowest, so your p50 becomes their p90.
  • Retry storms and metastable failure. A slow dependency makes callers time out and , which adds load, which causes more . The system stays broken even after the original trigger is gone, because it has found a stable bad equilibrium (this is called ). That is why , (random delay) and exist, and why "just add retries" makes outages worse.
  • The lost-ack ambiguity. The request succeeded, but the response was lost. The caller cannot tell "not done" from "done, but the was lost". There is no protocol fix for this. The only resolution is an receiver keyed on a caller-supplied . This is the same problem that have. Synchronous calls do not escape it; they only hide it better. (This is Ravi's double charge.)

The RPC variants

Synchronous unary (one request, one reply): everything above.

Async / callback / future RPC decouples the caller's thread from the call, not the caller from the callee. The availability coupling is unchanged. It is a concurrency optimization, not a change of transport. Do not let the word "async" make you think you bought decoupling.

Streaming RPC (gRPC server-streaming, client-streaming, bidirectional) is the interesting one. HTTP/2 (the protocol under gRPC) has flow control through connection and stream windows: the receiver announces how much data it can accept, and the sender cannot exceed it. This is credit-based built into the transport, so rate is genuinely decoupled, up to the window size.

But availability and lifetime are not. The stream lives and dies with the connection. There is no store. If the receiver dies in the middle, everything in flight is gone, and there is no position to resume from unless the application built its own checkpointing on top.

The clean way to remember it: is a queue with zero durability and a lifetime tied to one connection. It is the right tool for continuous transfer between two live processes that can restart from a known point (replication, log tailing, model-inference streams). It is the wrong tool whenever "the was down for 20 minutes" must be survivable.

Where synchronous calls belong in a data platform

In the , not the . Trigger a job, check a status, fetch a schema from a schema registry, resolve a lookup. The moment you move records at volume, a synchronous call per record gives you the worst position on latency-versus-throughput (a network per record) and the worst availability coupling. If you must call a service once per record, batch the calls, so that you trade for deliberately and not by accident.