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.