Distributed & Decentralized Systems Curriculum
Time Causality Β· Architectural Models RPC

Key Question

What makes gRPC different from traditional RPC?

Deep Dive

gRPC is Google’s open-source RPC framework built on HTTP/2 and Protocol Buffers. It’s not just β€œRPC with Protobuf” β€” the streaming model fundamentally changes what’s possible.

Four Service Types

gRPC defines four kinds of RPC calls, each useful for different patterns.

1. Unary RPC β€” Standard request-response. Client sends one message, server replies with one.

  Client ─── Request ──►  Server
        ◄── Response ──

2. Server-Streaming β€” Client sends one request; server pushes a stream of responses.

  Client ─── Request ──►  Server
        ◄── Response 1 ──
        ◄── Response 2 ──
        ◄── Response 3 ──  (server closes stream)

Useful for: real-time feed, large result sets, progress updates.

3. Client-Streaming β€” Client pushes a stream of messages; server replies once.

  Client ─── Request 1 ──►  Server
        ─── Request 2 ──►
        ─── Request 3 ──►
        ◄── Response ────

Useful for: batch upload, sensor data, large file upload.

4. Bidirectional Streaming β€” Both sides send independent streams simultaneously.

  Client ─── Request 1 ──►  Server
        ─── Request 2 ──►
        ◄── Response 1 ──
        ─── Request 3 ──►
        ◄── Response 2 ──
        ◄── Response 3 ──

Useful for: chat, real-time collaboration, streaming analytics.

Unary vs Streaming: Latency Difference

Unary (one request per item):
  β”Œβ”€β”€β”  ── Req 1 ──►  β”Œβ”€β”€β”
  β”‚C Lβ”‚  ◄── Res 1 ──  β”‚S  β”‚
  β”‚L Iβ”‚  ── Req 2 ──►  β”‚E  β”‚  (N round trips = 2N network delays)
  β”‚I Eβ”‚  ◄── Res 2 ──  β”‚R  β”‚
  β”‚E Nβ”‚  ── Req 3 ──►  β”‚V  β”‚
  β”‚N Tβ”‚  ◄── Res 3 ──  β”‚ER β”‚
  β””β”€β”€β”˜                  β””β”€β”€β”˜

Streaming (single connection, multiplexed):
  β”Œβ”€β”€β”  ──── Req 1 ────►  β”Œβ”€β”€β”
  β”‚C Lβ”‚  ──── Req 2 ────►  β”‚S  β”‚
  β”‚L Iβ”‚  ◄─── Res 1 ────  β”‚E  β”‚  (1 RTT setup + stream)
  β”‚I Eβ”‚  ──── Req 3 ────►  β”‚R  β”‚
  β”‚E Nβ”‚  ◄─── Res 2 ────  β”‚V  β”‚
  β”‚N Tβ”‚  ◄─── Res 3 ────  β”‚ER β”‚
  β””β”€β”€β”˜                      β””β”€β”€β”˜

With unary, N requests = N separate HTTP/2 round trips. With streaming, the connection is established once and messages flow without per-request overhead.

Why HTTP/2 Matters

FeatureHTTP/1.1HTTP/2
MultiplexingNo (one request per connection)Yes (multiple streams on one connection)
Header compressionNoYes (HPACK)
Server pushNoYes
Binary framingNoYes
Stream priorityNoYes

gRPC multiplexes many RPC calls over a single TCP connection. No head-of-line blocking. No connection storms when a service restarts.

Real-World gRPC

gRPC is the default RPC framework for:

  • Microservices β€” standard internal communication (e.g., Netflix, Square, Lyft)
  • Kubernetes APIs β€” kubectl talks to the API server via gRPC
  • etcd β€” distributed key-value store uses gRPC for all client communication
  • Envoy proxy β€” uses gRPC for xDS configuration APIs
service UserService {
  // Unary
  rpc GetUser (GetUserRequest) returns (User);
  
  // Server-streaming: get all users matching a filter
  rpc ListUsers (ListUsersRequest) returns (stream User);
  
  // Bidirectional streaming: real-time user updates
  rpc WatchUsers (stream UserAction) returns (stream UserEvent);
}

Check Your Understanding

  1. A ride-sharing app needs to send real-time GPS coordinates from driver to server every 100ms. Which gRPC streaming type should they use?

  2. Why does HTTP/2 multiplexing reduce the β€œthundering herd” problem when a microservice restarts?

  3. You have an existing REST API. Should you rewrite it in gRPC? When is it worth the migration cost?

The β€œSo What?”

gRPC’s streaming model isn’t just a performance optimization β€” it enables entirely new patterns: real-time dashboards, streaming ML inference, live collaboration, event sourcing. If you’re building microservices today, gRPC is the default choice for internal communication. REST still wins for external/public APIs where browsers and mobile clients are the consumers (but even that is changing with gRPC-Web and Connect).


✏️ Exercises

Exercises: Architectural Models & RPC

Exercise 1: Architecture Choice

A team is building a file-sharing application for 100,000 users. Files are read-heavy (mostly downloads), users are geographically distributed, and there is no budget for centralized infrastructure.

  1. Which architectural model (client-server, P2P, multi-tier, hybrid) would you recommend?
  2. What specific design decisions would you make to handle the read-heavy workload?
  3. What are the top three problems you’d need to solve?

Exercise 2: RPC Call Failure Analysis

A client calls deductBalance(userID: "u42", amount: 50.00) via RPC. The client stub sends the request to the server. For each scenario below, state what happens and whether the client’s balance is correct:

  1. The client stub marshals the request and sends it. The server receives it, unmarshals, calls the function (which deducts $50 from the DB), but the server crashes before sending the response.
  2. The server receives the request, processes it, and sends the response. The response is lost in the network. The client times out and throws an exception.
  3. The client sends the request. The server processes it and sends the response. The client receives the response. Everything works β€” but the network duplicates the request and the server processes it twice.

Exercise 3: Protobuf Field Evolution

You have this protobuf schema deployed in production:

message Order {
  string order_id = 1;
  string user_id = 2;
  float total = 3;
}

You want to add a string coupon_code = 4 field. Some old server instances still running don’t know about field 4.

  1. Will old servers crash when they receive a message with coupon_code set?
  2. A client built from the old schema processes a message that has coupon_code. What does the client see?
  3. What happens if you later delete field 3 (total) and reuse its number for a new int64 total_cents = 3?
πŸ‘οΈ View Solutions

Solutions: Architectural Models & RPC

Exercise 1: Architecture Choice

  1. Recommended model: Hybrid with P2P as the primary model β€” users share files directly with each other β€” plus a small set of super-peers (or a lightweight tracker) for discovery.

    • Pure P2P (BitTorrent-style) handles read-heavy workloads naturally: popular files are replicated across many peers, distributing the download load.
    • A tracker (a lightweight centralized component) solves the discovery problem β€” where to find each file.
    • A small number of β€œseed” servers can ensure unpopular files remain available (no single point of failure because seeds are optional).
  2. Design decisions:

    • Chunk files into pieces so peers can download different chunks from different peers in parallel.
    • Use content-addressed storage (hash of the file content as the identifier) to verify integrity.
    • Implement a tit-for-tat incentive mechanism (you share, you get faster downloads).
  3. Top three problems:

    • Discovery: How do peers find each other and learn which files are available? (Tracker nodes + DHT)
    • Churn: Peers join and leave constantly. How do you maintain availability of rare files? (Replication factor, redundancy)
    • Trust: How do you prevent peers from serving corrupted data? (Content hashing, cryptographic verification)

Exercise 2: RPC Call Failure Analysis

  1. Correct? β€” No. The server deducted $50 but crashed before the client got confirmation. The client assumes the call failed and may retry. You now have a duplicate deduction (or at least a $50 mismatch unless the operation is idempotent). This is the classic β€œat-most-once vs at-least-once” dilemma. Solution: make deductBalance idempotent (using a request ID or idempotency key).

  2. Correct? β€” The server did deduct $50. The client got an exception and doesn’t know whether the deduction happened. This is the exactly-once is impossible problem in distributed systems. The client must check the balance or retry with idempotency.

  3. Correct? β€” No. The balance is deducted twice β€” $100 total instead of $50. The server processed the request twice because the transport layer delivered a duplicate. Solution: deduplication at the server (track recently seen request IDs).

Key insight: In all three cases, the client cannot trivially know the correct balance without additional mechanisms (idempotency keys, at-least-once delivery with dedup, transactional outboxes).


Exercise 3: Protobuf Field Evolution

  1. Will old servers crash? No. Protobuf is designed for forward compatibility. Old servers that don’t know about field 4 will simply ignore the unknown bytes. The message is self-describing enough that unknown fields are skipped during deserialization.

  2. What does the old client see? The coupon_code field will be absent (default empty string). The client sees order.coupon_code == "". The data is not lost β€” if the client re-serializes the message, the unknown bytes for field 4 are preserved and passed through.

  3. What happens if you delete field 3 and reuse its number? Disaster. Old servers still running will interpret the new total_cents field as the old total field and read garbage. Never reuse a field number. Instead, mark the field as reserved 3; in the new schema β€” this prevents accidental reuse.