Module A-5·23 min read

Implementing backpressure when upstream ingestion velocity outpaces downstream write capacity — socket floods, TCP buffers, and the drain event contract.

JJS
Written by Jatin Jain Saraf · Senior Software Engineer

Module 4 — The HTTP/TCP Subsystem & Ingestion Backpressure

What this module covers: Backpressure is the mechanism that prevents a fast producer from overwhelming a slow consumer. In a blockchain indexer, the producer is the network delivering transaction events at 50K/sec. The consumer is your PostgreSQL database accepting 5K writes/sec. Without backpressure, your process accumulates an unbounded in-memory queue and eventually OOMs. This module covers how TCP flow control works at the kernel level, how Node.js stream backpressure mirrors it at the application level, and the precise implementation of backpressure for high-throughput ingestion pipelines.


The Problem: Producers Outpacing Consumers

A blockchain indexer at peak load has an inherent mismatch:

  • Producer: blockchain full node pushing 50,000 transaction events/second over TCP
  • Consumer: PostgreSQL database accepting 8,000–12,000 writes/second

Without backpressure, the process does this:

javascript

The fix requires two layers of backpressure working together:

  1. TCP flow control — the kernel tells the sender to slow down when the receive buffer is full
  2. Stream backpressure — Node.js pauses the socket read when the downstream consumer is busy

Understanding how these two layers interact is the key to building a pipeline that never OOMs under load.


TCP Flow Control: The Kernel's Backpressure

TCP has backpressure built in via the receive window mechanism.

Every TCP ACK includes a window field — the number of bytes the receiver is willing to accept. When the kernel's receive buffer fills up, it advertises a smaller window to the sender. When the buffer is full, it advertises zero window — the sender must stop completely.

text

The critical insight: TCP flow control automatically propagates backpressure upstream. When Node.js stops reading from the socket (because downstream is slow), the kernel receive buffer fills, the window shrinks to zero, and the blockchain full node is forced to stop sending. The backpressure is communicated all the way back to the data source — no data is dropped, it is simply slowed.

The mailbox analogy: A zero TCP window is the postal equivalent of a mailbox so full the carrier physically can't fit another envelope through the slot — she doesn't lose your letter, she just waits on the porch until you empty the box.


Node.js Streams: Application-Level Backpressure

Node.js streams implement the same backpressure mechanism at the JavaScript level, mirroring TCP flow control.

The highWaterMark (HWM)

Every writable stream has a highWaterMark — the maximum number of bytes (or objects) it is willing to buffer before signalling that it is full.

javascript

The write() Return Value: The Backpressure Signal

When you write to a writable stream, it returns a boolean:

  • true — buffer is below HWM, safe to continue writing
  • false — buffer has reached HWM, you should stop writing
javascript

Production war story: A UPI settlement reconciliation service ignored the boolean return of write() entirely — every call site treated stream.write(data) as fire-and-forget. Normally this was harmless because the downstream consumer kept pace. Then a partner bank's API started responding in a degraded 2 seconds instead of the usual 50ms, at the same time festival load pushed reconciliation volume to several times normal. With no code checking the return value and no drain listener anywhere, the service's internal write buffer grew unbounded for 40 straight minutes — invisible in dashboards that only tracked request rate, not buffer depth — until the process hit its memory ceiling and was OOM-killed at 3 AM. The fix was mechanical (route every write through the writeWithBackpressure helper above) but the incident illustrated the core risk: write()'s return value isn't optional telemetry, it's the only signal standing between a slow downstream and an unbounded buffer.

The drain Event: The Resume Signal

When the stream's internal buffer empties below HWM after being full, it emits drain. This is your signal to resume writing.

javascript

cork() / uncork(): Micro-Batching Small Writes

When an ingestion pipeline emits many small writes in a tight loop — one write() per parsed transaction, for example — each call can incur its own overhead in the underlying writable (a syscall for a socket, a query round-trip for some DB drivers). writable.cork() tells the stream to buffer everything written until a matching uncork(), so those small writes get coalesced into fewer, larger underlying operations.

javascript

Two things to keep in mind:

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.