3 min read
On this page

gRPC & Protocol Buffers

Overview

gRPC is a high-performance RPC framework developed by Google that uses HTTP/2 for transport and Protocol Buffers (protobuf) for serialization. It is designed for efficient service-to-service communication where performance, type safety, and streaming matter more than browser compatibility.

When to Use gRPC vs REST

Choose gRPC when:
  - Services communicate internally (backend-to-backend)
  - Low latency and high throughput are critical
  - You need streaming (real-time data, long-lived connections)
  - Strict API contracts with type safety are important
  - Payload size matters (protobuf is 3-10x smaller than JSON)
  - Polyglot environment (auto-generated clients in many languages)

Choose REST when:
  - Building public-facing APIs consumed by third parties
  - Browser clients need direct access (gRPC-Web exists but adds friction)
  - Human readability of requests and responses matters
  - Team is more familiar with REST conventions
  - Simple CRUD operations dominate
  - Caching via HTTP infrastructure is important

Real-world pattern:
  REST for external APIs (public clients, mobile apps)
  gRPC for internal service-to-service communication

Protocol Buffers

Protocol Buffers (protobuf) is a language-neutral, platform-neutral serialization format. You define message structures in .proto files, and the compiler generates code in your target language.

Message Definition

syntax = "proto3";

package ecommerce;

message Product {
  string id = 1;
  string name = 2;
  string description = 3;
  int32 price_cents = 4;
  Category category = 5;
  repeated string tags = 6;
  optional string image_url = 7;

  enum Category {
    CATEGORY_UNSPECIFIED = 0;
    ELECTRONICS = 1;
    CLOTHING = 2;
    BOOKS = 3;
  }
}

message ProductList {
  repeated Product products = 1;
  string next_page_token = 2;
}

Key Protobuf Concepts

Field numbers (= 1, = 2, etc.):
  - Identify fields in the binary format
  - Must never be reused or changed once in production
  - Numbers 1-15 use 1 byte (use for frequently set fields)
  - Numbers 16-2047 use 2 bytes

Scalar types:
  int32, int64, uint32, uint64  Integer types
  float, double                 Floating point
  bool                          Boolean
  string                        UTF-8 text
  bytes                         Arbitrary binary data

Composite types:
  repeated    List of values (repeated string tags)
  map         Key-value pairs (map<string, int32> scores)
  oneof       Exactly one of several fields
  message     Nested message types

Default values in proto3:
  Numbers default to 0, strings to "", bools to false
  You cannot distinguish "explicitly set to default" from "not set"
  Use optional keyword or wrapper types when this distinction matters

Binary Encoding

JSON representation (89 bytes):
  {"id":"prod-123","name":"Headphones","price_cents":4999,"category":"ELECTRONICS"}

Protobuf binary (approximately 35 bytes):
  Compact binary encoding using field numbers and wire types.
  Not human-readable, but 2-5x smaller than JSON.
  Faster to serialize and deserialize.

Performance comparison (approximate):
  Serialization:   Protobuf is 2-6x faster than JSON
  Deserialization:  Protobuf is 2-6x faster than JSON
  Message size:     Protobuf is 2-5x smaller than JSON

Service Definitions

gRPC services are defined in .proto files alongside message types.

service ProductService {
  // Unary RPC: single request, single response
  rpc GetProduct(GetProductRequest) returns (Product);

  // Server streaming: single request, stream of responses
  rpc ListProducts(ListProductsRequest) returns (stream Product);

  // Client streaming: stream of requests, single response
  rpc UploadProductImages(stream ImageChunk) returns (UploadResult);

  // Bidirectional streaming: both sides send streams
  rpc ProductChat(stream ChatMessage) returns (stream ChatMessage);
}

message GetProductRequest {
  string product_id = 1;
}

message ListProductsRequest {
  string category = 1;
  int32 page_size = 2;
  string page_token = 3;
}

Streaming Patterns

Server Streaming

Use case: Real-time stock price updates, live search results

Client sends one request, server streams multiple responses.

  Client -> GetPriceUpdates(symbol: "AAPL")
  Server -> { price: 185.50, timestamp: ... }
  Server -> { price: 185.75, timestamp: ... }
  Server -> { price: 185.60, timestamp: ... }
  ...continues until client cancels or server closes

Real-world: Google Pub/Sub uses server streaming for message delivery.

Client Streaming

Use case: File upload, batch data ingestion

Client sends multiple messages, server responds once when complete.

  Client -> ImageChunk { data: [bytes 0-1023] }
  Client -> ImageChunk { data: [bytes 1024-2047] }
  Client -> ImageChunk { data: [bytes 2048-3071] }
  Client -> (end of stream)
  Server -> UploadResult { url: "https://...", size_bytes: 3072 }

Bidirectional Streaming

Use case: Chat, collaborative editing, game state synchronization

Both sides send messages independently on the same connection.

  Client -> ChatMessage { text: "Hello" }
  Server -> ChatMessage { text: "Hi there!" }
  Client -> ChatMessage { text: "What's the status?" }
  Server -> ChatMessage { text: "Processing your request..." }
  Server -> ChatMessage { text: "Done! Here are the results." }

Real-world: Google Cloud Speech-to-Text uses bidirectional streaming
for real-time transcription (audio in, text out simultaneously).

Backward Compatibility

Protobuf is designed for schema evolution, but you must follow rules to maintain compatibility.

Safe Changes

Safe (backward compatible):
  - Adding new fields (use new field numbers)
  - Adding new enum values
  - Adding new RPC methods to a service
  - Changing a field from required to optional (proto2)
  - Adding a new service

Unsafe (breaking):
  - Removing a field (old clients still send it, new code ignores it)
  - Changing a field number (breaks binary encoding)
  - Changing a field type (int32 to string)
  - Renaming a field (breaks JSON serialization, not binary)
  - Reusing a deleted field number

Rules for safe evolution:
  1. Never reuse field numbers
  2. Mark removed fields as reserved
  3. Add new fields with new numbers
  4. Never change the type of existing fields

Reserved Fields

message Product {
  reserved 3, 7;                    // Reserved field numbers
  reserved "old_price", "legacy_sku"; // Reserved field names

  string id = 1;
  string name = 2;
  // field 3 was old_price, removed in v2
  int32 price_cents = 4;
  // ...
}

The compiler will reject any attempt to use reserved numbers or names,
preventing accidental reuse.

gRPC Features

Metadata & Interceptors

Metadata: Key-value pairs sent with requests (like HTTP headers)
  Used for: authentication tokens, request IDs, tracing context

Interceptors: Middleware for gRPC calls
  Used for: logging, authentication, retries, metrics

Common interceptor chain:
  Request -> Auth -> Logging -> Rate Limiting -> Handler
  Response <- Metrics <- Error Handling <- Handler

Deadlines & Cancellation

Deadlines propagate across service boundaries:

  Service A calls Service B with deadline: 5 seconds
    Service B calls Service C with remaining deadline: 3 seconds
      If Service C takes too long, the entire chain cancels

This prevents cascading timeouts from consuming resources.
Always set deadlines on gRPC calls. There is no default timeout.

Error Handling

gRPC status codes (different from HTTP):
  OK                  Success
  CANCELLED           Client cancelled the request
  INVALID_ARGUMENT    Client sent bad input
  NOT_FOUND           Requested resource does not exist
  ALREADY_EXISTS      Resource already exists (conflict)
  PERMISSION_DENIED   Caller lacks permission
  UNAUTHENTICATED     No valid credentials provided
  RESOURCE_EXHAUSTED  Rate limit or quota exceeded
  INTERNAL            Server bug
  UNAVAILABLE         Service is temporarily unavailable
  DEADLINE_EXCEEDED   Operation timed out

Include error details for debugging:
  Status code + error message + optional structured details

Health Checking

gRPC has a standard health checking protocol:

service Health {
  rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
  rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
}

Load balancers and service meshes use this to route traffic
only to healthy instances.

Real-World Usage

Google uses gRPC extensively for internal service communication. Most Google Cloud APIs offer gRPC endpoints alongside REST.

Netflix migrated from REST to gRPC for inter-service communication, citing significant latency improvements and better type safety across their polyglot microservices.

Spotify uses gRPC for backend service communication, taking advantage of streaming for real-time audio and event delivery.

Square uses gRPC internally and contributed significantly to the gRPC ecosystem, including the Go implementation.

gRPC-Web

Browsers cannot directly use gRPC (no HTTP/2 frame-level access).
gRPC-Web is a protocol that works in browsers via a proxy.

Architecture:
  Browser -> gRPC-Web -> Envoy Proxy -> gRPC Backend

Limitations:
  - Only unary and server streaming (no client or bidi streaming)
  - Requires a proxy (Envoy, grpc-web-proxy)
  - Smaller ecosystem than REST for frontend development

Alternative: Use REST/GraphQL for browser clients, gRPC internally.

Common Pitfalls

  • Not setting deadlines: gRPC calls without deadlines can hang indefinitely. Always set a deadline.
  • Ignoring backward compatibility rules: Reusing field numbers or changing field types breaks all existing clients silently.
  • Using gRPC for public APIs: Most third-party developers expect REST with JSON. gRPC adds friction for external consumers.
  • Large messages: gRPC has a default 4MB message size limit. For large payloads, use streaming or increase the limit explicitly.
  • Not implementing health checks: Without standard health checks, load balancers cannot properly route traffic.
  • Forgetting about observability: gRPC calls are binary and harder to debug than REST. Invest in proper logging, tracing, and metrics from the start.

Key Takeaways

  • gRPC excels at internal service-to-service communication where performance, type safety, and streaming are important.
  • Protocol Buffers provide compact serialization (2-5x smaller than JSON) with code generation in many languages.
  • gRPC supports four communication patterns: unary, server streaming, client streaming, and bidirectional streaming.
  • Always follow backward compatibility rules for protobuf schemas: never reuse field numbers, never change field types, mark removed fields as reserved.
  • Set deadlines on every gRPC call. Deadline propagation prevents cascading failures.
  • Use REST for public-facing APIs and browser clients. Use gRPC for internal backend communication.