Performance

HTTP/2 & gRPC

Multiplexed connections and binary protocols that dramatically reduce request overhead.

Overview

HTTP/2 (RFC 7540, 2015) multiplexes multiple requests over a single TCP connection, eliminating head-of-line blocking and reducing connection overhead from HTTP/1.1. gRPC (Google, 2015) uses HTTP/2 as its transport and Protocol Buffers as its serialisation format, providing strongly-typed, high-performance RPC with streaming support. Both are binary protocols significantly faster than HTTP/1.1 with JSON.

Origin

HTTP/1.1 (1997) required one request per TCP connection without pipelining, causing browsers to open 6 connections per domain. SPDY (Google, 2009) introduced multiplexing and compression; it became the basis for HTTP/2 (IETF, 2015). Google released gRPC in February 2015 as an open-source version of their internal Stubby RPC framework. HTTP/3 (2022) replaced TCP with QUIC to eliminate TCP head-of-line blocking.

Examples

gRPC service definition and TypeScript server implementation

// order_service.proto
// syntax = "proto3";
// package orders.v1;
//
// service OrderService {
//   rpc GetOrder (GetOrderRequest) returns (Order);
//   rpc ListOrders (ListOrdersRequest) returns (stream Order);
//   rpc CreateOrder (CreateOrderRequest) returns (Order);
// }
//
// message GetOrderRequest { string order_id = 1; }
// message Order {
//   string id = 1;
//   string customer_id = 2;
//   int64 total_cents = 3;
//   string status = 4;
//   google.protobuf.Timestamp created_at = 5;
// }

// Generated with: npx grpc_tools_node_protoc ...
import * as grpc from '@grpc/grpc-js';
import { OrderServiceImplementation } from './generated/order_grpc_pb';
import { Order, GetOrderRequest } from './generated/order_pb';

const implementation: OrderServiceImplementation = {
  getOrder(call: grpc.ServerUnaryCall<GetOrderRequest, Order>, callback) {
    const orderId = call.request.getOrderId();
    const order = db.orders.findById(orderId);
    if (!order) {
      callback({ code: grpc.status.NOT_FOUND, message: 'Order not found' });
      return;
    }
    const response = new Order();
    response.setId(order.id);
    response.setTotalCents(order.totalCents);
    callback(null, response);
  },
};

Protocol Buffers serialisation is 3-10x smaller than equivalent JSON and 5-10x faster to serialise/deserialise. The .proto file is the source of truth for the API contract; code is generated for both client and server, preventing interface drift.

HTTP/2 server push and connection reuse in Node.js

import http2 from 'http2';
import fs from 'fs';

const server = http2.createSecureServer({
  key: fs.readFileSync('./key.pem'),
  cert: fs.readFileSync('./cert.pem'),
  allowHTTP1: true, // Fallback for HTTP/1.1 clients
});

server.on('stream', (stream, headers) => {
  const path = headers[':path'];

  if (path === '/') {
    // Server Push: push CSS and JS before browser requests them
    stream.pushStream({ ':path': '/static/app.css' }, (err, pushStream) => {
      if (!err) {
        pushStream.respondWithFile('./public/static/app.css', {
          'content-type': 'text/css',
          'cache-control': 'public, max-age=31536000',
        });
      }
    });

    stream.respondWithFile('./public/index.html', {
      'content-type': 'text/html; charset=utf-8',
      ':status': 200,
    });
  }
});

server.listen(443);
// HTTP/2 multiplexes all streams on one TLS connection
// Multiple concurrent requests do not open multiple connections

Server push (HTTP/2 PUSH_PROMISE) proactively sends resources the browser will need, eliminating the waterfall of HTML parse -> discover CSS link -> request CSS. In practice, server push is often misconfigured; the Early Hints header (HTTP 103) is a simpler alternative.

Use Cases

  • 01Internal microservice communication where gRPC's generated clients, streaming, and binary encoding outperform REST+JSON
  • 02Mobile APIs where Protocol Buffers' smaller payload size reduces data usage on metered connections
  • 03Bidirectional streaming with gRPC for real-time server-to-client updates (stock prices, live leaderboards)
  • 04High-throughput service-to-service calls where JSON parsing overhead is measurable and Protocol Buffers decode faster

When Not to Use

  • //Do not use gRPC for public browser APIs; browser JavaScript does not support gRPC directly (requires grpc-web and an Envoy proxy)
  • //Do not use gRPC when the team needs simple curl-based debugging; gRPC requires grpcurl or BloomRPC; REST+JSON is easier to inspect and test
  • //Do not use HTTP/2 server push speculatively; it can cause wasted bandwidth if the client already has the pushed resource cached

Technical Notes

  • HTTP/2 head-of-line blocking: HTTP/2 eliminates application-level HOL blocking (multiple streams on one connection) but TCP HOL blocking remains (one lost packet stalls all streams). HTTP/3 uses QUIC (UDP) to eliminate TCP HOL blocking; Cloudflare and Google have widely deployed HTTP/3
  • gRPC streaming types: unary (single request/response), server streaming (one request, stream of responses), client streaming (stream of requests, one response), bidirectional streaming (both sides stream simultaneously)
  • Protocol Buffers field numbering: field numbers 1-15 encode in one byte; 16-2047 in two bytes. Reserve numbers 1-15 for the most frequently occurring fields to minimise serialised message size
  • gRPC-Gateway (grpc-ecosystem/grpc-gateway) generates a RESTful JSON gateway from .proto service definitions, allowing the same backend to serve both gRPC and REST clients with a single source of truth