Назад в блог
Бэкенд

gRPC: What It Actually Solves, and When to Reach for It

A contract-first, binary, HTTP/2 RPC framework. Where it beats REST, where it hurts, and how to run it in production without regret.

Опубликовано
Время чтения
9 мин чтения
Автор
Yakhya

Most teams discover gRPC the same way: the service mesh has grown to thirty services, JSON serialization shows up in the CPU profile, and nobody can say with confidence what fields a given endpoint returns anymore. gRPC is Google's answer to that, and it is worth understanding as three separate ideas that happen to ship together — a schema language, a binary wire format, and an HTTP/2-based transport with streaming built in.

The contract comes first

In gRPC, the .proto file is the source of truth. You describe messages and services, run protoc, and get typed clients and server stubs in every language you need. The API is not documentation that drifts away from the code — it is the thing the code is generated from. That single property removes an entire class of integration bugs, and it is the reason to adopt gRPC even before you care about performance.

protobuf
syntax = "proto3";
package payments.v1;

service PaymentService {
  rpc CreatePayment(CreatePaymentRequest) returns (Payment);
  rpc WatchPayment(WatchPaymentRequest) returns (stream PaymentEvent);
}

message CreatePaymentRequest {
  string idempotency_key = 1;
  string account_id      = 2;
  int64  amount_minor    = 3;   // never float for money
  string currency        = 4;   // ISO 4217
}

message Payment {
  string id     = 1;
  Status status = 2;
  enum Status {
    STATUS_UNSPECIFIED = 0;
    PENDING            = 1;
    SETTLED            = 2;
    FAILED             = 3;
  }
}

Field numbers, not field names, go on the wire. That is what makes Protobuf both compact and evolvable: you can rename a field freely, add new ones at any time, and old clients simply ignore what they do not know. The rules to internalize are short — never reuse a field number, never change a field's type, reserve numbers you delete, and always give enums a zero value meaning "unspecified".

Four call types, one transport

Because gRPC sits on HTTP/2, a single TCP connection multiplexes many concurrent calls without head-of-line blocking at the request level, and streaming is a first-class concept rather than something bolted on. You get unary (request/response), server streaming (one request, many responses — think live order book or progress feed), client streaming (many requests, one response — think bulk upload or metric ingestion), and bidirectional streaming (chat, telemetry, long-running negotiation).

That transport also gives you deadlines that propagate. A client says "this call must finish in 300ms", and every downstream hop inherits the remaining budget. In a REST estate, timeouts are set per-hop and multiply into disasters; in gRPC, a deadline is part of the call and cancellation travels the whole chain. If you take one operational habit from gRPC, take this one.

Where gRPC genuinely wins

  • Service-to-service traffic inside your own network, where both ends are yours and you control the deploy cycle.
  • Polyglot estates — a Go service, a Node service, and a Python model server all speaking one generated contract.
  • High call volume, where Protobuf's smaller payloads and cheaper parsing translate directly into lower CPU and tail latency.
  • Streaming workloads that would otherwise become a bespoke WebSocket protocol nobody documents.
  • Anywhere you want breaking changes to fail at build time instead of at 3am.

Where it hurts

Browsers cannot speak raw gRPC — you need gRPC-Web plus a proxy (Envoy, or a framework equivalent), which drops support for client and bidirectional streaming. Debugging is no longer curl and eyeballs; you need grpcurl, and payloads are unreadable in a packet capture. Load balancing is a real decision: because HTTP/2 connections are long-lived and multiplexed, an L4 balancer will pin all of a client's traffic to one backend, so you need L7-aware balancing or client-side balancing via a service mesh. And third-party consumers will almost always prefer a REST/JSON surface.

A pragmatic default: gRPC between your own services, REST or GraphQL at the public edge. One gateway translates, and neither side is compromised.

Production checklist

  1. 01Version your packages in the path (payments.v1) and treat v2 as a new package, not an edit.
  2. 02Store .proto files in one repository, lint them in CI with Buf, and fail the build on breaking changes.
  3. 03Set deadlines on every call — a call without a deadline is a leak waiting for its moment.
  4. 04Use interceptors for auth, tracing, retries, and metrics so business handlers stay clean.
  5. 05Map errors to gRPC status codes deliberately, and put machine-readable detail in error details rather than in a prose message.
  6. 06Enable keepalive pings and tune them with your load balancer's idle timeout, or you will chase phantom connection resets.
  7. 07Expose health checks via the standard grpc.health.v1 service so Kubernetes probes and the mesh agree with each other.

The honest summary: gRPC is not faster in a way most CRUD applications will ever notice. Its real value is a contract that cannot silently rot, deadlines that propagate, and streaming that does not require inventing a protocol. Those are architectural properties, and they compound as a system grows.

Теги
gRPCProtobufHTTP/2MicroservicesAPI Design
Продолжить чтениеВсе статьи
Yakhya
© 2026 Yakhya. Все права защищены.