SD System Design In Depth
0%
lesson system-designscalingcommunication

Service-to-service communication

Module 3.1: Network Protocols & Service Communication Updated Sep 17, 2026
📄 Cheat Sheet
Synchronous and Asynchronous Communication between Microservices
Arpit Bhayani Synchronous and Asynchronous Communication between Microservices Watch on YouTube ↗

Arpit works through when to use blocking request/response versus message-based communication with concrete examples, covering coupling, failure propagation and consistency, the core of this unit.

0:00 / --:--

Under the Hood: Synchronous RPC vs. Asynchronous Messaging

When monolithic applications decompose into distributed services, communication transitions from in-memory function pointers (sub-microsecond) to network sockets subject to non-deterministic latency, packet loss, and connection pool exhaustion. Choosing between synchronous RPC and asynchronous messaging fundamentally dictates system resilience.

Communication Topologies Compared

flowchart TD subgraph SyncTopology ["1. Synchronous RPC (Tight Temporal Coupling)"] S1["Order Service"] -->|"HTTP / gRPC (Blocking)"| S2["Inventory Service"] S2 -->|"Blocking Call"| S3["Payment Gateway"] Note1["Thread held open. Failure at S3 cascades upstream, exhausting thread pools."] end subgraph AsyncTopology ["2. Asynchronous Event-Driven (Temporal Decoupling)"] O1["Order Service"] -->|"Publish 'order.created'"| Bus[("Durable Event Log: Kafka")] Bus -->|"Pull batch"| I1["Inventory Worker"] Bus -->|"Pull batch"| P1["Payment Worker"] Note2["No caller thread waits on downstream work. Slowdowns become queue lag, bounded by broker retention and capacity, and lag is user-visible delay."] end

Under the Hood: Production Communication Invariants

1. Distributed Context & Deadline Propagation

Every inbound user request must receive a unique W3C Trace Context (traceparent) and an absolute client deadline (e.g., grpc-timeout: 500m). If an upstream client specifies a 500ms timeout, and service A spends 450ms processing, downstream calls from service A to service B must only receive the remaining 50ms deadline. Continuing to execute downstream work after an upstream caller has already disconnected wastes CPU and database connections.

2. Bounded Connection Pools and Keep-Alive

Creating TCP/TLS handshakes for every inter-service call introduces substantial latency overhead. Production services maintain persistent connection pools with HTTP/2 multiplexing or TCP keep-alive, strictly bounding max connections to avoid overwhelming downstream servers with connection spikes.

Worked example: deadline budget across a call chain

A user request enters the API with a 1,000 ms deadline. The API calls the order service, which calls inventory and then pricing sequentially.

  • API spends 50 ms on auth and parsing, then calls order with a remaining deadline of 950 ms.
  • Order spends 30 ms, calls inventory. Inventory normally answers in 20 ms but is overloaded and hangs.
  • Without propagation: order uses a fixed 2 s client timeout to inventory. The user's request fails at 1,000 ms, but order keeps a thread and a connection blocked for another 1 s, and inventory keeps working on a request nobody wants. Under load, this wasted work is what exhausts thread pools.
  • With propagation: order passes the remaining 920 ms. gRPC does this automatically by sending the grpc-timeout header; each hop subtracts elapsed time. When the deadline passes, every hop cancels its work.

A good rule is to set each hop's timeout to the smaller of its own latency target (for example p99.9 plus margin) and the remaining caller deadline.

Choosing a style

NeedSynchronous (HTTP or gRPC)Asynchronous (queue or log)
Caller needs the result to respondNatural fitAwkward; needs request/reply correlation
Downstream may be down for minutesCaller fails too unless it has a fallbackMessages wait in the broker
Traffic burstsDownstream must absorb the burst in real timeBroker buffers; consumers work at their own pace
Many consumers of one factCaller must call each onePublish once; each subscriber reads independently
DebuggingSingle trace, simple to followNeeds correlation IDs across producer and consumer
Delivery semanticsCaller knows success or timeout (ambiguous)At-least-once; duplicates and reordering possible

Mechanics that matter

  • Connection reuse: a new TCP plus TLS 1.3 connection costs at least 2 round trips before the first byte of the request. Pooled keep-alive connections or HTTP/2 multiplexing avoid this per call.
  • Bounded pools: cap connections per destination so a slow dependency cannot absorb all of a caller's threads (the bulkhead pattern).
  • Trace context: propagate the W3C traceparent header on calls and in message headers so one request can be followed through queues.
  • Transactional outbox: to update a database and publish an event without losing either, write the event to an outbox table in the same transaction and have a relay publish it.

How real systems do it

Google's internal RPC system (Stubby, the predecessor of gRPC) made deadline propagation a default. Uber, Netflix and LinkedIn use Kafka for asynchronous fan-out of events such as trips, plays and profile updates, while keeping request/response RPC for user-facing reads.

Check yourself Question 1 of 3
Remaining deadline wins apply
From: Worked example: deadline budget across a call chain

A user request arrives with a 300 ms deadline. The API spends 120 ms, then the order service spends 100 ms before calling inventory. Inventory's own timeout target (p99.9 plus margin) is 150 ms. What timeout should the order service use for that call?

Key Takeaways

Synchronous RPC creates tight temporal coupling where downstream latency cascades into upstream thread exhaustion.
Asynchronous messaging via durable brokers decouples service availability and absorbs traffic spikes into queue lag, within broker retention limits; it does not fit calls where the user needs an immediate answer (for example payment authorisation).
Propagate distributed trace context and deadlines across every network hop to avoid executing dead requests.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue