Web Streams performance crisis
Node.js Web Streams run 100× slower than Node streams due to promise overhead, complex state machines JavaScript Conferences by GitNationTL;DW
- Web Streams readable stream implementation is 100× slower than Node.js streams in many benchmarks due to excessive promises, microtask deferrals, and complex state machines.
- Readable Streams spec creates hidden unbounded buffers; when tee() splits a stream, unread branches accumulate data with no backpressure, causing memory exhaustion.
- Transform streams break backpressure signaling when async transforms operate without proper backpressure coordination between writer and reader sides, enabling unbounded buffer growth.
- James Snell's experimental API redesigns streams as async iterables using language primitives instead of classes, eliminating controllers, readers, locks, and promise overhead.
- Batching multiple chunks in a single array (e.g., returning 10 chunks at once instead of one-per-promise) reduces overhead by orders of magnitude versus single-chunk yielding.
- Pull-through transforms only execute when data is pulled, eliminating unnecessary work and internal buffering compared to push-based transform queue accumulation.
- New design enforces explicit backpressure by default with strict, block, drop-oldest, and drop-newest policies; Readable Streams allows implementations to ignore signals entirely.
- Benchmarks show 20–120× speedups: simple pipelines ~20× faster, chained transforms 70–120× faster, using just async iterators and synchronous generators without additional abstractions.
- Next.js server-side rendering created 250+ Readable Stream instances per request (one per chunk), demonstrating ergonomic inefficiency that lighter iterator-based design solves.
- Design eliminates promise overhead (multi-thousand promises per thousand chunks in current spec) by using language primitives; only dependency is AbortSignal, available on all runtimes.
TL;DW
- Web Streams readable stream implementation is 100× slower than Node.js streams in many benchmarks due to excessive promises, microtask deferrals, and complex state machines.
- Readable Streams spec creates hidden unbounded buffers; when tee() splits a stream, unread branches accumulate data with no backpressure, causing memory exhaustion.
- Transform streams break backpressure signaling when async transforms operate without proper backpressure coordination between writer and reader sides, enabling unbounded buffer growth.
- James Snell's experimental API redesigns streams as async iterables using language primitives instead of classes, eliminating controllers, readers, locks, and promise overhead.
- Batching multiple chunks in a single array (e.g., returning 10 chunks at once instead of one-per-promise) reduces overhead by orders of magnitude versus single-chunk yielding.
- Pull-through transforms only execute when data is pulled, eliminating unnecessary work and internal buffering compared to push-based transform queue accumulation.
- New design enforces explicit backpressure by default with strict, block, drop-oldest, and drop-newest policies; Readable Streams allows implementations to ignore signals entirely.
- Benchmarks show 20–120× speedups: simple pipelines ~20× faster, chained transforms 70–120× faster, using just async iterators and synchronous generators without additional abstractions.
- Next.js server-side rendering created 250+ Readable Stream instances per request (one per chunk), demonstrating ergonomic inefficiency that lighter iterator-based design solves.
- Design eliminates promise overhead (multi-thousand promises per thousand chunks in current spec) by using language primitives; only dependency is AbortSignal, available on all runtimes.
James Snell benchmarks Web Streams across Node.js, browsers, and edge runtimes—finding 1–2 orders of magnitude slowdowns from excessive promise allocation, microtask deferrals, and hidden buffering. He proposes an async-iterator-based alternative with explicit back-pressure that delivers 70–120× speedups on chained transforms and 20–22× on pipelines.
