gRPC vs REST: When the Performance Difference Actually Matters

Real-world benchmarks comparing gRPC and REST in microservices architectures, with guidance on when protocol choice creates meaningful business impact.

#grpc#rest#microservices#performance
Cover image for the article: gRPC vs REST: When the Performance Difference Actually Matters

I have watched teams spend months migrating perfectly functional REST APIs to gRPC because someone read a blog post claiming "10x performance improvement." The reality is more nuanced. In our production environment with 340 microservices, the switch to gRPC delivered measurable gains in exactly three scenarios — and made things worse in two others. Here is the data-driven framework we use to decide which protocol fits which service boundary.

The Setup: Controlled Benchmarks on Production Traffic

We did not benchmark in isolation. We deployed dual-protocol endpoints on 12 representative services and ran production traffic through both simultaneously for 30 days. Each service handled between 2,000 and 180,000 requests per second. The test infrastructure:

  • Compute: EKS clusters on m7g.2xlarge (Graviton3)
  • Networking: Service mesh via Istio 1.20 with mTLS
  • Languages: Go (7 services), Rust (3 services), TypeScript (2 services)
  • Payload sizes: 200B to 2MB across different endpoints
  • Measurement: Custom eBPF-based latency instrumentation at the kernel level

Benchmark Architecture

The Raw Numbers

Small Payloads (<1KB) — Internal Service Communication

MetricREST (JSON)gRPC (Protobuf)Delta
P50 Latency2.1ms0.8ms-62%
P99 Latency8.4ms2.3ms-73%
Throughput (req/s)42,000127,000+202%
Payload Size847B312B-63%
CPU Usage (sender)12%4%-67%
CPU Usage (receiver)18%7%-61%

For small, frequent inter-service calls, gRPC dominates. The combination of HTTP/2 multiplexing, binary serialization, and persistent connections creates a compound advantage.

Large Payloads (100KB-2MB) — Data Pipeline Transfers

MetricREST (JSON)gRPC (Protobuf)Delta
P50 Latency45ms38ms-16%
P99 Latency120ms98ms-18%
Throughput (req/s)3,2003,800+19%
Bandwidth Usage1.8 Gbps1.1 Gbps-39%

The advantage narrows significantly with large payloads. Network I/O dominates, and the serialization overhead becomes proportionally smaller. The bandwidth savings from protobuf remain meaningful for cost.

Streaming Workloads — Real-time Event Feeds

This is where gRPC's bidirectional streaming creates an entirely new capability:

// event_stream.proto
syntax = "proto3";

service EventStream {
  // Server-streaming: client subscribes, server pushes events
  rpc Subscribe(SubscribeRequest) returns (stream Event) {}
  
  // Bidirectional: real-time collaboration channel
  rpc Collaborate(stream ClientMessage) returns (stream ServerMessage) {}
}

message Event {
  string id = 1;
  string type = 2;
  int64 timestamp_ms = 3;
  bytes payload = 4;
  map&#x3C;string, string> metadata = 5;
}

message SubscribeRequest {
  repeated string topics = 1;
  int64 since_timestamp_ms = 2;
  int32 max_batch_size = 3;
}

With REST, you would need WebSockets (losing HTTP semantics), SSE (unidirectional only), or polling (wasted bandwidth). gRPC streaming over HTTP/2 gives you typed, multiplexed, bidirectional streams with backpressure built in.

When gRPC Makes Things Worse

Scenario 1: Browser-Facing APIs

gRPC-Web exists, but it is a compatibility layer, not native gRPC. It requires a proxy (Envoy or grpc-web), adds latency, and loses bidirectional streaming. Our frontend team measured:

MetricREST (native fetch)gRPC-Web (via Envoy)
Time to First Byte42ms67ms
Bundle Size Impact0KB+48KB (protobuf.js)
DebuggingChrome DevToolsCustom tooling needed
CachingHTTP cache headersNot cacheable

For browser clients, REST with JSON remains superior in developer experience, tooling, and actual end-user performance when you factor in the proxy hop.

Scenario 2: Third-Party API Consumers

If external developers consume your API, gRPC creates friction:

  • Requires protobuf compilation toolchain
  • Limited language support compared to "curl + JSON"
  • API exploration tools (Postman, Insomnia) have weaker gRPC support
  • Documentation tooling is less mature than OpenAPI/Swagger

We maintain REST for all externally-facing APIs regardless of internal protocol choice.

Implementation: The Dual-Protocol Gateway Pattern

Our production architecture uses gRPC internally and REST externally, with an API gateway handling translation:

package gateway

import (
    "context"
    "encoding/json"
    "net/http"
    
    "google.golang.org/grpc"
    "google.golang.org/protobuf/encoding/protojson"
    
    pb "company/services/orders/v1"
)

// RESTHandler wraps gRPC service calls with JSON serialization
type RESTHandler struct {
    ordersClient pb.OrderServiceClient
    marshaler    protojson.MarshalOptions
}

func NewRESTHandler(grpcConn *grpc.ClientConn) *RESTHandler {
    return &#x26;RESTHandler{
        ordersClient: pb.NewOrderServiceClient(grpcConn),
        marshaler: protojson.MarshalOptions{
            EmitUnpopulated: false,
            UseProtoNames:   true,
        },
    }
}

func (h *RESTHandler) GetOrder(w http.ResponseWriter, r *http.Request) {
    orderID := r.PathValue("id")
    
    // Internal call uses gRPC - sub-millisecond serialization
    resp, err := h.ordersClient.GetOrder(r.Context(), &#x26;pb.GetOrderRequest{
        OrderId: orderID,
    })
    if err != nil {
        writeGRPCError(w, err)
        return
    }
    
    // External response uses JSON - human-readable, cacheable
    data, err := h.marshaler.Marshal(resp)
    if err != nil {
        http.Error(w, "serialization error", http.StatusInternalServerError)
        return
    }
    
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Cache-Control", "public, max-age=60")
    w.Write(data)
}

This pattern gives us the best of both worlds: gRPC performance for the 98% of traffic that is service-to-service, and REST compatibility for external consumers and browser clients.

The Decision Framework

After 30 days of production data across 12 services, here is our protocol decision matrix:

Use CaseProtocolRationale
Internal service-to-servicegRPC62-73% latency reduction, 3x throughput
High-frequency small payloadsgRPCCPU savings compound at scale
Real-time streaminggRPCNative bidirectional with backpressure
Browser-facing APIsRESTNative fetch, caching, DevTools
External/partner APIsRESTDeveloper experience, discoverability
File uploads > 10MBRESTSimpler chunked transfer, resumability
CRUD with HTTP cachingRESTCache-Control semantics are free performance

Connection Management: The Hidden Cost

One thing benchmarks miss: gRPC connection management in Kubernetes is non-trivial. HTTP/2 connections are long-lived and multiplexed, which means Kubernetes services with round-robin DNS do not load-balance correctly — all requests go to the first resolved pod.

# Istio DestinationRule for proper gRPC load balancing
apiVersion: networking.istio.io/v1beta1
kind: DestinationRule
metadata:
  name: order-service
spec:
  host: order-service.production.svc.cluster.local
  trafficPolicy:
    loadBalancer:
      simple: ROUND_ROBIN
    connectionPool:
      http:
        h2UpgradePolicy: UPGRADE
        maxRequestsPerConnection: 1000  # Force connection cycling
      tcp:
        maxConnections: 100
        connectTimeout: 5s

Without a service mesh or client-side load balancing (like grpc-go's round-robin resolver), you will get severe hot-spotting on individual pods.

Load Balancing Topology

Cost Impact at Scale

For our 340-service platform processing 2.1 billion internal requests/day:

  • Bandwidth savings (protobuf vs JSON): 4.2 TB/day less cross-AZ transfer = $3,800/month saved
  • CPU reduction (serialization): 340 fewer vCPUs needed = $18,200/month saved
  • Latency improvement: 14ms average reduction per hop × 4.2 avg hops = 59ms end-to-end improvement

Total annual infrastructure savings from gRPC adoption: $264K. But this only materialized because we adopted it selectively for high-volume internal paths, not universally.

Conclusion

The gRPC vs REST debate is not a technical holy war — it is an engineering economics question. gRPC delivers measurable, significant performance gains for high-frequency internal communication between services you control. REST remains the correct choice for external APIs, browser clients, and any endpoint where developer experience and HTTP semantics matter more than raw throughput.

The mistake I see teams make is treating this as an all-or-nothing decision. Run both. Use gRPC where the numbers justify it, REST where the ecosystem demands it, and a translation gateway at the boundary. Measure in production, not in synthetic benchmarks, and let the data drive protocol choices at each service boundary.

Comments

    No comments yet. Be the first to share your thoughts.