Protocol Buffers vs JSON-over-HTTP, Kafka consumer group mechanics for 500K msg/sec, and event sourcing for UPI transaction ledgers.
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:
- TCP connection (or TLS handshake if not keep-alive) — 1–3ms
- HTTP/1.1 header parsing — variable, grows with header count
JSON.parse()on request body — ~1ms per 100KB- Business logic
JSON.stringify()on response — ~0.5ms per 100KB- 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.
Generating Node.js code:
Size comparison for a typical transaction:
| Format | Encoded size | Parse time (1K messages) |
|---|---|---|
| JSON (human-readable) | 340 bytes | 18ms |
| JSON (minified) | 220 bytes | 12ms |
| Protobuf binary | 82 bytes | 2ms |
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.
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
Implementing a Streaming gRPC Server in Node.js
gRPC Client with Retry and Deadline
gRPC vs REST: Throughput Comparison
| Metric | REST + JSON | gRPC + Protobuf |
|---|---|---|
| Payload size (5KB object) | 5,000 bytes | ~850 bytes |
| Serialization (100K msg/sec) | ~500ms CPU | ~85ms CPU |
| Connections (1K clients) | 1K TCP sockets | 1 TCP socket (mux) |
| Latency (p99, same network) | 8ms | 2ms |
| Streaming | Workarounds (SSE, WebSocket) | Native (all 4 types) |
gRPC is the correct choice for inter-service communication at > 10K calls/second or when bidirectional streaming is required.
Kafka: Distributed Event Ledger
Kafka is a distributed log. Unlike a message queue (which deletes messages after consumption), Kafka retains messages for a configurable retention period. Every consumer reads from its own offset in the log — Kafka does not push messages to consumers; consumers pull.
Core Concepts
Topic: a named log. transactions, blocks, payment-events.
Partition: a topic is split into partitions for parallelism. Each partition is an ordered, immutable log. Within a partition, messages are ordered by offset. Across partitions, there is no ordering guarantee.
Consumer group: a set of consumers that collectively read a topic. Kafka assigns partitions to consumers — each partition is consumed by exactly one consumer in the group. If you have 8 partitions and 8 consumers: each consumer reads one partition. If you have 8 partitions and 4 consumers: each consumer reads two partitions.
Producer: Batching and Compression
Compression at 500K messages/second:
- LZ4: 2–3x compression, minimal CPU cost. Best for throughput.
- Snappy: 1.5–2x compression, very fast. Good default.
- GZIP: 3–5x compression, high CPU. Use for storage efficiency, not throughput.
Consumer Group: Partition Assignment and Rebalancing
Consumer Lag: The Critical Operational Metric
Consumer lag = (latest offset in topic) - (consumer's current offset).
Lag = 0: consumer is caught up. Lag > 0: consumer is falling behind. Lag growing: consumer cannot keep up with producer rate.
Event Sourcing for Payment Ledgers
Event sourcing stores every state change as an immutable event, instead of storing current state. The current state is derived by replaying all events.
Why event sourcing for payment ledgers:
-
Audit trail: every transaction is immutable. You can prove the exact sequence of operations that led to any balance.
-
Replay: if you discover a bug in your balance calculation logic, replay all events with the fixed logic to get correct current balances.
-
Temporal queries: what was account X's balance on March 15th? Replay events up to March 15th.
-
Kafka as the event store: Kafka's log retention makes it a natural event store.
Snapshots: Avoiding Full Replay
For accounts with millions of historical events, replaying from the beginning is impractical. Snapshots cache the balance at a point in time:
RabbitMQ vs Kafka: When Each Is Correct
| Kafka | RabbitMQ | |
|---|---|---|
| Model | Pull-based log | Push-based queue |
| Message retention | Configurable (days/weeks) | Deleted after consumption |
| Replay | Yes — replay from any offset | No — consumed messages are gone |
| Throughput | 1M+ messages/sec | ~50K messages/sec |
| Ordering | Within partition | Per-queue (with single consumer) |
| Consumer groups | Built-in | Via competing consumers |
| Use case | Event sourcing, audit logs, stream processing | Task queues, RPC, work distribution |
For a blockchain indexer: Kafka. You need replay capability (debug consumer bugs), high throughput (500K tx/sec), and consumer groups for parallel processing.
For payment notification emails: RabbitMQ. You need exactly-once delivery, message acknowledgement, dead letter queues, and retry logic. Kafka can do this but the configuration is more complex.
Production Incident: Kafka Partition Imbalance Causing Consumer Hotspot
Context: A payment event stream with 12 partitions, 12 consumer instances. Partition key was senderId. One major payment aggregator (a large e-commerce platform) sent 40% of all payment events. All events from this sender went to one partition.
What happened:
One consumer instance was processing 40% of total volume. Its consumer lag grew continuously. The other 11 instances were at 5% each. Rebalancing couldn't help — the problem was the partition key, not the number of consumers.
The fix — composite partition key:
Also added consumer lag alerting per partition (not just total lag) to detect hotspots early:
Summary
| Concept | Key Takeaway |
|---|---|
| Protobuf vs JSON | 4–5x smaller, 6–9x faster parse. Critical at > 100K msg/sec. |
| gRPC over HTTP/2 | Multiplexed streams, 4 call types. Native streaming support. 4x lower latency than REST. |
| gRPC server streaming | call.write() pushes to client. call.on('cancelled') for cleanup. |
| Kafka retention | Messages persist after consumption. Replay from any offset. |
| Consumer groups | Partitions assigned to consumers. One partition → one consumer per group. |
| Consumer lag | Growing lag = consumer can't keep up. Scale consumers or optimize processing. |
| Partition key | High-cardinality composite keys prevent hotspots. Never use a skewed key. |
acks: -1 | Wait for all replicas before confirming publish. Required for durability. |
| LZ4 compression | Best throughput/CPU ratio. Use for high-volume streams. |
| Event sourcing | Immutable event log. Full history. Replay to any point in time. |
| Snapshots | Cache balance at N events to avoid full replay on every query. |
| Kafka vs RabbitMQ | Kafka for event streams/replay. RabbitMQ for task queues with ACK semantics. |
You now have the communication backbone. Module 11 covers how to keep the code clean as these systems grow — DDD applied to banking ledgers, dependency injection that lets you swap transports, and CQRS that separates the write model from the read model.
Next: Module 11 — Enterprise Architecture for State-Heavy Systems: DDD & Clean Architecture →
In high-throughput inter-service communication, what is the primary advantage of using Protocol Buffers over JSON?
When using Kafka as a message broker, what is the relationship between partitions and consumer groups?
Which of the following best describes the core philosophy behind "Event Sourcing" for a payment ledger?
Test your knowledge with more question sets
Sign in to access a wider variety of questions and get notified when new practice sets are added to this module.
Sign in & RegisterDiscussion
0Join the discussion