gRPC
Remote Procedure Call (RPC) over HTTP/2 with Protocol Buffers.
What is gRPC
An open-source, high-performance RPC framework. A client calls a method on a server on a different machine as if it were a local object — gRPC handles the networking. Runs on HTTP/2, uses Protocol Buffers as its Interface Definition Language (IDL) and wire format by default.
- Home / docs: https://grpc.io/docs/what-is-grpc/introduction/
- Origin: Google’s internal Stubby, generalized and open-sourced as gRPC (2015). The “g” is a rotating backronym.
The whole architecture at a glance — the layered stack (Application → Stub → gRPC Runtime → Transport) plus the pluggable pieces this note drills into below (interceptors, name resolution / service discovery, load balancing, retry / deadline / cancellation):

Source: Ivy Zhuang’s talk — gRPC overview (gRPCConf 2024).
Start here: the Service Definition
Everything flows from the contract. gRPC is built around the idea of defining a service — specifying the methods that can be called remotely with their parameters and return types.
- Write a
.protofile — the contract (message= data shapes,service= the RPCs). - Run
protoc(with the gRPC plugin) → generates client stubs and server interfaces in your language. - Server implements the interface; client calls the stub.
- Because both sides are generated from the same
.proto, they can’t drift out of sync.
Core concepts — Service definition
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse);
}
message HelloRequest { string greeting = 1; }
message HelloResponse { string reply = 1; }projects/slack-clone/message-service.Protobuf’s dual role
Protocol Buffers is both:
- the IDL (Interface Definition Language) — the language you define messages and services in, and
- the serialization mechanism — a compact, strongly-typed binary format on the wire (smaller/faster than JSON text).
Use proto3 with gRPC. → https://protobuf.dev
Why the binary encoding is small
JSON ships the field names as text on every message. Protobuf ships a tag number instead, plus varint length encoding.
{"id":"123","name":"john doe"} // JSON, ~30 bytes, names on the wire
0A 03 31 32 33 12 08 6A 6F 68 6E ... // protobuf, ~15 bytes: <tag><len><bytes>Each field on the wire is (field_number << 3) | wire_type. That’s why field numbers
are the permanent identity of a field and renaming is free but renumbering is a breaking
change.
- Encoding spec — the actual byte layout
- Proto3 language guide
- Schema evolution rules — add fields with new tags = backward compatible; never reuse a retired tag; use
reserved
Four kinds of service methods
| Type | Shape | Proto signature |
|---|---|---|
| Unary | 1 req → 1 resp | rpc SayHello(HelloRequest) returns (HelloResponse); |
| Server streaming | 1 req → stream of resp | rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse); |
| Client streaming | stream of req → 1 resp | rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse); |
| Bidirectional streaming | stream ↔ stream (independent read/write) | rpc BidiHello(stream HelloRequest) returns (stream HelloResponse); |
The stream keyword on the request/response side is what distinguishes them. Bidi streams
are independent — order is preserved within each direction, but the two directions
aren’t lock-stepped.
Core concepts — the four service methods
RPC lifecycle
Core concepts — RPC life cycle. Key topics:
- Deadlines / timeouts — client sets how long it’ll wait; server can check if it’s still worth continuing. This is a deadline (absolute point in time), propagated across hops — better than a per-hop timeout. Deadlines blog
- Cancellation — either side can cancel; ends the RPC, no more work done.
- RPC termination — client and server decide independently that the call is complete.
- Metadata — key/value info about the call, separate from the payload (auth tokens, tracing). guide
- Channels — a client’s virtual connection to a server (host/port) with configurable state (backed by ≥1 HTTP/2 connection).
Error model
gRPC has its own status codes, not HTTP status codes: OK, INVALID_ARGUMENT,
NOT_FOUND, DEADLINE_EXCEEDED, UNAVAILABLE, RESOURCE_EXHAUSTED, etc. UNAVAILABLE
is the retryable one.
Interceptors (the middleware story)
Client- and server-side hooks for auth, logging, metrics, retries — the gRPC equivalent of HTTP middleware. Interceptors guide
Retries & hedging
gRPC can retry failed calls and hedge (race duplicate calls to cut tail latency) declaratively via service config — no app code. Hedging is a general distributed-systems technique, not a gRPC feature; see Request Hedging for the concept and tradeoffs. gRPC retry/hedging guide
Why HTTP/2 matters
gRPC rides on HTTP/2, which gives it:
- Multiplexing — many concurrent calls (streams) over one TCP connection; no app-layer head-of-line blocking between calls.
- Native bidirectional streaming — not just request/response.
- Binary framing — efficient, compact (pairs well with binary protobuf).
- Header compression (HPACK — Header Compression for HTTP/2) — less per-call overhead.
- Persistent connections — avoids repeated handshakes.
HTTP/2 — RFC 9113 · deeper: High Performance Browser Networking, HTTP/2 chapter (Ilya Grigorik, free online)
Where to use it
- Internal, service-to-service comms in microservices — strong typing catches errors at compile time; binary is far more efficient than JSON/HTTP (benchmarks cite up to ~10× throughput). Best when latency is network-dominated.
- Not for public-facing APIs / clients you don’t control — tooling is less ubiquitous than JSON-over-HTTP, and browsers can’t speak raw gRPC.
- Common pattern: gRPC internal, REST external.
Browser gap → gRPC-Web
Browsers can’t do raw gRPC (no access to HTTP/2 frames / trailers). gRPC-Web is a JS client + a proxy (Envoy or the standalone proxy) that translates.
Load balancing gRPC (the sharp edge at scale)
A naive Layer 4 (L4, the transport layer — TCP/UDP) load balancer (LB) breaks gRPC balancing: because gRPC keeps one long-lived HTTP/2 connection and multiplexes all calls over it, connection-level (L4) balancing pins a client to one backend and every request rides that same pin. You need request-level balancing at Layer 7 (L7, the application layer — HTTP), or client-side balancing.
- gRPC has built-in client-side load balancing (pick_first, round_robin) + xDS (the discovery protocol family behind Envoy — Listener/Route/Cluster/Endpoint Discovery Service,
*DS) for dynamic discovery/config from a control plane (the same API Envoy uses). - gRPC load balancing blog · Custom LB policies · gRPC + xDS
Talks & deeper resources
(cited by title/speaker/venue; verify the exact URL before relying on it)
- Ivy Zhuang — gRPC overview — YouTube. SWE at Google & gRPC Java maintainer.
- Official gRPC guides index — auth, retries, keepalive, health checking, reflection, deadlines.
Quick self-check (recall from memory)
- Why can’t a browser call a gRPC service directly, and what’s the fix?
- Why does a plain L4 load balancer distribute gRPC connections fine but requests badly?
- What’s safe vs breaking when editing a
.proto? (rename field? renumber? delete?) - Deadline vs timeout — why does gRPC prefer deadlines across a call chain?
- When would you pick server-streaming over just returning a
repeatedfield?