Stream: backpressure
Механизм авторегуляции потока данных: если приёмник не успевает писать, источник приостанавливается. Без backpressure быстрый read в медленный write взорвёт память.
Что это
В цепочке Readable → Writable (через pipe) скорости могут не совпадать: чтение с диска идёт MB/s, запись по сети — KB/s. Без регуляции байты копятся в буфере writable и память растёт. Backpressure — обратное давление: writable говорит «стоп», readable приостанавливает чтение.
У каждого стрима в конструкторе есть два параметра:
highWaterMark— буфер > этого → пауза источникаlowWaterMark— буфер < этого → упреждающее чтение
Дефолт highWaterMark = 16 КБ (или 16 объектов в object mode).
Как срабатывает
const writable = fs.createWriteStream('out');
const ok = writable.write(chunk);
// false — буфер переполнен, перестать писать
// true — можно дальше
if (!ok) {
readable.pause(); // притормозить источник
writable.once('drain', () => readable.resume); // буфер опустел → продолжить
}
При использовании pipe всё это происходит автоматически:
fs.createReadStream('big.mp4')
.pipe(zlib.createGzip)
.pipe(res); // pipe сам отслеживает backpressure
В цепочке pipe данные тянутся «с конца»: самый последний writable просит у предыдущего, тот у источника. Это pull-модель.
pipeline вместо ручного pipe
pipe не закрывает потоки при ошибке: если Readable бросил error, Writable остаётся открытым → утечка дескрипторов. Решение — stream.pipeline:
const { pipeline } = require('node:stream/promises');
await pipeline(
fs.createReadStream('input'),
zlib.createGzip,
fs.createWriteStream('output.gz')
);
// при ошибке: все потоки закрываются автоматически
Подводные камни
pipeне закрывает события сам: при ручномpipeвсё равно надо вешатьon('end'),on('close'),on('error'). Иначе утечка- Ошибка без обработчика →
EventEmitterбросаетthrow, в синхронном контексте процесс падает. В async — зависит - Игнор
writereturning false → потеря backpressure, накопление в RAM - Object mode: highWaterMark считается в штуках объектов, не байтах
req/resHTTP — duplex на одном сокете,end/closeсинхронизированы;allowHalfOpenвлияет на закрытие
🎓 Источник: HTTP Server in Node.js — req, res, sockets, and streams
- 📅 2020-01-16 · YouTube ·
PDR5hcV4a_0 - Тезисы:
- Стримы — ленивая обработка: ничего лишнего не читается, данные тянутся с конца цепочки
- У каждого стрима в конструкторе
highWaterMark/lowWaterMark: буфер > high → пауза источника, < low → упреждающее чтение - Каждый стрим в цепочке вырабатывает backpressure независимо
- Если хочешь весь файл —
fs.readFileбыстрее чем стрим; стрим оправдан только при обработке/преобразовании reqиres— это один и тот же сокет (duplex),endиcloseсинхронизированы- Необработанный
errorв синхронном контексте роняет процесс; в асинхронном может не свалить
- Цитата:
«Если количество забуферизированных данных, которые никто еще не смог прочитать, будет выше, чем high watermark, то стрим приостанавливается и не читает данные из источника дальше.»