Module A-11·27 min read

Protocol Buffers vs JSON-over-HTTP, Kafka consumer group mechanics for 500K msg/sec, and event sourcing for UPI transaction ledgers.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module 10 — High-Performance IPC: gRPC, Kafka & Event Streams

What this module covers: When your blockchain indexer needs to stream 500K events/second to a downstream analytics service, JSON-over-HTTP REST adds measurable overhead at every layer: HTTP/1.1 per-request connection overhead, JSON serialization CPU cost, and protocol parsing latency. gRPC over HTTP/2 with Protocol Buffers eliminates most of this. Kafka adds persistence, replay, and fault tolerance. This module covers the exact performance differences, the implementation of streaming gRPC endpoints in Node.js, Kafka consumer group mechanics for high-throughput consumption, and event sourcing for payment ledgers that need full state replay.


The Baseline: JSON-over-HTTP REST Overhead

Before measuring alternatives, establish what REST actually costs at high throughput:

Per-request overhead:

  1. TCP connection (or TLS handshake if not keep-alive) — 1–3ms
  2. HTTP/1.1 header parsing — variable, grows with header count
  3. JSON.parse() on request body — ~1ms per 100KB
  4. Business logic
  5. JSON.stringify() on response — ~0.5ms per 100KB
  6. HTTP response headers serialization

For a payment gateway doing 10,000 inter-service calls/second:

  • JSON serialization at 5KB average payload: ~50ms total per second
  • HTTP header parsing: ~30ms total per second
  • TCP connection overhead (without keep-alive): 1,000–3,000ms per second

With HTTP keep-alive: TCP overhead drops. JSON overhead remains. At 500K calls/second, JSON becomes the dominant cost.


Protocol Buffers: Binary Serialization

Protocol Buffers (protobuf) is a binary serialization format. You define a schema in a .proto file, and a code generator produces type-safe encoder/decoder functions.

Protobuf ships a pre-packed container with a manifest taped to the side — the field numbers in the wire format tell the decoder exactly where everything is, so it never has to re-parse structure it already knows. JSON, by contrast, re-packs your entire house into loose grocery bags on every single delivery, even when all you needed was a spoon — every key is re-sent as a string, every value re-parsed from text, on every message, forever.

protobuf

Generating Node.js code:

bash

Size comparison for a typical transaction:

FormatEncoded sizeParse time (1K messages)
JSON (human-readable)340 bytes18ms
JSON (minified)220 bytes12ms
Protobuf binary82 bytes2ms

Protobuf is 4–5x smaller and 6–9x faster to parse. At 500K messages/second, this difference is:

  • JSON: 110MB/sec serialization + 6,000ms CPU/sec for parsing
  • Protobuf: 41MB/sec serialization + 1,000ms CPU/sec for parsing

The bandwidth reduction matters for inter-datacenter links. The CPU reduction matters for everything.

Schema Evolution: The Rules That Keep Consumers From Silently Breaking

Protobuf's wire format identifies fields by number, not by name — the field number, not amount or status, is what's actually encoded on the wire. That makes evolution fast (you can rename a field in the .proto file freely, since only the number matters), but it also makes one mistake catastrophic: reusing or renumbering a field number. An old consumer that hasn't been redeployed will happily decode whatever new data shows up at a field number it already knows, under the old field's name and type — no error, no crash, just quietly wrong data.

protobuf

The rules, in order of how expensive it is to violate them:

  1. Never reuse a field number, even for an unrelated field, even years later. Mark removed fields reserved <number>; so the compiler itself refuses to let anyone reassign it.
  2. Never change a field's number once it has shipped to any consumer — renumbering amount from 5 to 12 is indistinguishable, on the wire, from deleting field 5 and adding a new field 12.
  3. New fields are always additive, with new numbers, and should be tagged optional (proto3 field presence) so old producers that don't set them don't break new consumers expecting a value.
  4. Changing a field's type (e.g. int32int64) is usually safe for compatible wire types, but changing semantics at the same number (e.g. status from an enum to a free-text string) is exactly the failure mode below.

Enforcing this beyond code review — the schema registry: in a system with more than one consumer team, "reserve removed fields" is a rule people forget under deadline pressure. A schema registry — Confluent Schema Registry (for Avro/Protobuf over Kafka) or the Buf Schema Registry for protobuf specifically — makes evolution rules mechanical instead of a promise: every schema push is checked for backward compatibility against the previous version before it's allowed to register, and incompatible changes (a reused field number, a changed type) are rejected at CI time, before a producer ever ships them.

Production incident: a producer redeploy needed a new network_id field on Transaction and, under time pressure, reused field number 5 instead of allocating the next free one — field 5 had been amount since the schema's first version. Every unpatched consumer (several services hadn't redeployed yet) decoded the new network_id string as if it were amount, because as far as the wire format and the old .proto file were concerned, field 5 was still amount. Settlement totals silently accumulated garbage values for hours — the numbers were wrong, but nothing errored, because protobuf has no way to know field 5 "should" mean one thing forever. A nightly reconciliation job comparing settlement totals against the blockchain's own ledger caught the discrepancy. The fix going forward: reserved blocks for every removed or reassigned field number, plus a schema registry compatibility check wired into CI so a reused field number fails the build instead of reaching production.


gRPC: HTTP/2 + Protocol Buffers

gRPC is an RPC framework that uses HTTP/2 for transport and Protocol Buffers for serialization. It provides:

  • HTTP/2 multiplexing: multiple streams over a single TCP connection
  • Bidirectional streaming: client and server can both stream data simultaneously
  • Type safety: generated stub code handles serialization
  • Load balancing: built-in support for multiple backends

The Four gRPC Call Types

protobuf

Sign in to keep reading

The rest of this module is free — sign in with Google to unlock it and track your progress.

Sign in & Register

Discussion

0

Join the discussion

Loading comments...

© 2026 Jatin Jain Saraf (JJS). All rights reserved.