Як виникає backpressure?
Коротке нагадування визначення
Backpressure (зворотний тиск) - це ситуація, коли споживач (Writable) не встигає обробляти дані з тією швидкістю, з якою виробник (Readable) їх генерує.
Ключова причина виникнення
Невідповідність швидкостей потоків:
| Потік | Що робить | Можлива проблема |
|---|---|---|
Readable | генерує дані (наприклад, читає файл, мережу, базу) | надто швидко |
Writable | приймає й записує дані (наприклад, пише у файл, HTTP, базу) | надто повільно |
Коли Writable не встигає, його внутрішній буфер заповнюється.
І тоді Node.js зупиняє читання з джерела, щоб не "залити" систему даними.
Як це виглядає "усередині"
Кожен Writable Stream має внутрішній буфер (highWaterMark).
Приклад:
const stream = fs.createWriteStream('out.txt', { highWaterMark: 16 * 1024 });Тут highWaterMark = 16 KB - ліміт, скільки даних потік може тримати в буфері.
Коли ти викликаєш:
const ok = stream.write(chunk);то:
- якщо буфер не переповнений →
ok = true - якщо буфер переповнений →
ok = false
А це і є момент виникнення backpressure. Це сигнал: «стоп, не пиши більше, поки я не звільнюся».
У який момент це відбувається
Схематично:
Readable (виробляє 100 КБ/сек)
↓
Writable (встигає записувати 30 КБ/сек)
↓
Буфер Writable переповнюється → backpressureУ цей момент:
.write()повертаєfalse;- Node.js призупиняє читання (
readable.pause()); - Коли Writable скидає буфер на диск → подія
drain; - Node.js відновлює читання (
readable.resume()).
Приклад, де backpressure виникає явно
const fs = require('fs');
const readable = fs.createReadStream('big.txt');
const writable = fs.createWriteStream('copy.txt', { highWaterMark: 1024 }); // буфер 1 KB
readable.on('data', (chunk) => {
const ok = writable.write(chunk);
console.log('Пишемо...', ok ? 'ОК' : 'Буфер переповнений!');
if (!ok) {
readable.pause(); // призупиняємо читання
writable.once('drain', () => readable.resume());
}
});
readable.on('end', () => writable.end());Тут writable.write() повертає false,
коли потік не встигає - це і є момент виникнення backpressure.
У ланцюжках потоків (pipe() і pipeline())
Якщо ти використовуєш:
readable.pipe(transform).pipe(writable);то Node.js робить те саме автоматично:
- якщо
writableповертаєfalse→ читання зупиняється; - щойно
drain- читання відновлюється.
Тобто backpressure вбудований у систему pipe() і не потребує ручного контролю.
Фактори, що посилюють backpressure
| Причина | Приклад |
|---|---|
| Повільний запис на диск | HDD, мережеві сховища |
| Стиснення чи шифрування в Transform-потоках | zlib, crypto |
| Повільний відгук сервера під час HTTP-запису | res.write() повільніший за читання |
Великий розмір чанків чи малий highWaterMark | надто швидке накопичення буфера |
Складна синхронна логіка в _write() | блокує event loop |
Візуалізація процесу
┌────────────────────────────────┐
│ Readable Stream │
└──────────────┬─────────────────┘
│
▼
Буфер Writable (16 KB)
┌──────────────────────────────┐
│ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ │ ← заповнений
└──────────────────────────────┘
│
write() → false (Backpressure!)
│
↓ чекати drain() → resume()Коротко й просто
Backpressure виникає тоді, коли Writable Stream не встигає обробляти вхідні дані, і повідомляє Readable Stream, щоб той призупинився, поки буфер не звільниться.
Як із цим боротися
- Використовувати
pipe()чиpipeline()- вони автоматично керують backpressure - Налаштувати
highWaterMarkпід свою задачу - Робити Transform-потоки ефективними й неблокуючими
- Уникати синхронних операцій усередині потоків
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.