Worker Threads в Node.js: спасение от блокировки event loop на 10GB CSV и крипте
-
Event loop в Node.js - это святое. Один синхронный forEach по 10GB CSV или крипто-хэш по мегабайтам данных - и твой сервер висит, как легаси на IE6. Worker Threads решают эту проблему, вынося тяжёлые вычисления в отдельные потоки.
Зачем это нужно? Потому что дефолтный thread pool из 4 потоков от libuv быстро кончается на CPU-intensive задачах. Парсинг огромных файлов или крипто-операции блочат не только event loop, но и worker pool. С Worker Threads ты спавнишь изолированные V8-инстансы с собственным event loop - и основной поток дышит свободно.
Почему event loop блокируются даже на ‘асинхронных’ задачах
Node.js кажется асинхронным раем, но под капотом прячется libuv с фиксированным пулом из 4 worker threads. Эти ребята берут на себя блокирующий I/O: fs.readFileSync, crypto.pbkdf2, zlib deflate - всё, что C++ код делает синхронно. Но если твой JavaScript-код тяжёлый - цикл по 10GB CSV с парсингом строк или миллион хэшей - он жрёт один из этих 4 потоков.
Представь: у тебя API для парсинга отчётов. Клиент кидает 10GB CSV, ты запускаешь sync-парсер - event loop встал, ответы не летят, 502 ошибки сыплются. То же с крипто: pbkdf2 на 1M итераций по массиву ключей. Thread pool забит, новые запросы в очередь. А если нагрузка - кластер из 8 ядер, каждый воркер спавнит подзадачи, и GC улетает на 70% CPU от кросс-трэд утечек.
- Блокировка event loop: Длинный JS-цикл (>50мс) фризит callbacks, throughput падает до 0.
- Worker pool исчерпан: 4 потока на все DNS, crypto, file I/O - одна тяжёлая задача монополизирует.
- Кластерные грабли: Воркеры Node + worker_threads = double GC на SharedArrayBuffer, утечки в postMessage.
Проблема Без Worker Threads С Worker Threads 10GB CSV парсинг Event loop фриз на 30с+ Основной поток свободен, прогресс по сообщениям Крипто 1M ключей Thread pool забит, новые API висят Параллельный поток, общий результат по shared memory CPU load 100% на main thread Распределено, GC не взлетает Worker Threads под капотом: не child_process, а лёгкие братья
Worker Threads - это не форк процессов, как child_process. Каждый worker - отдельный V8 изолят с event loop, но в одном OS-процессе. Память шейрится через SharedArrayBuffer + Atomics, данные передаются копированием или transfer. Спавн лёгкий: 10-20мс vs 100мс+ на child_process.
В одном файле описываешь и main, и worker: if (isMainThread) { … } else { … }. Общаешься через parentPort.postMessage(). Worker умирает сам, когда код кончился и нет таймеров. Но нюанс: копирование больших объектов - overhead, используй transferable objects для ArrayBuffer.
- Создание: new Worker(__filename, { workerData: bigArray.buffer })
- Shared memory: new SharedArrayBuffer(size), Atomics для синхронизации
- Lifecycle: worker.on(‘message’), worker.on(‘exit’) - чистый ресурсный менеджмент
- isMainThread: API для проверки контекста в одном файле
Вот пример для крипто:
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); if (isMainThread) { const worker = new Worker(__filename, { workerData: { keys: arrayOfKeys } }); worker.on('message', (result) => console.log('Готово:', result)); } else { const hashes = workerData.keys.map(key => crypto.pbkdf2Sync(...)); parentPort.postMessage(hashes); }Парсинг 10GB CSV без смерти сервера
10GB файл - это не шутки: sync чтение через fs.createReadStream с парсингом строк в цикле забьёт event loop на минуты. Worker берёт поток на себя: читает чанками, парсит, отправляет прогресс. Основной поток отвечает клиенту ‘Обработка запущена’ и слушает обновления.
Важно: не передавай весь файл в workerData - копирование убьёт память. Передавай путь к файлу строкой, пусть worker сам fs.open. Для прогресса шлите { progress: 0.47 } каждые 100MB. На выходе - агрегированный JSON или вставка в DB батчами.
- Чанкинг: fs.createReadStream в worker, парсинг по строкам с csv-parser.
- Прогресс: parentPort.postMessage({ loaded: bytesRead / totalSize })
- Результат: Собранный объект или transfer ArrayBuffer для больших данных.
- Error handling: worker.on(‘error’) в main, terminate() если таймаут.
Таблица сравнения для 10GB CSV:
Подход Время обработки Event loop блок? Память Sync в main 5мин Да, полный DoS 2GB+ Libuv pool 4мин Частично, queue 1.5GB Worker Thread 3мин Нет 1GB (shared) Крипто-операции: pbkdf2 и SHA не для main thread
Crypto в Node - CPU-киллер: pbkdf2Sync(1M итераций) на 10k ключах - секунды фриза. Worker Threads идеальны: весь crypto.createHash, scrypt, pbkdf2 в изоляте. SharedArrayBuffer для ключей - Atomics.add для счётчика прогресса.
Грабли: postMessage копирует объекты, не уничтожай оригиналы - утечка. Используй worker.terminate() после finish. В кластере - по 1-2 worker на core, не больше, иначе GC overhead.
- Батчинг: Разбей на 1k ключей за раз, message по батчам.
- Transferable: keys.buffer для zero-copy передачи.
- Параллелизм: Несколько workers на multi-core, load balance по CPU.
// В worker const { salt, iterations } = workerData; const result = []; for (let i = 0; i < 10000; i++) { result.push(crypto.pbkdf2Sync(passwords[i], salt, iterations, 64, 'sha512')); } parentPort.postMessage(result.map(h => h.toString('hex')));Когда Worker Threads бьют по производительности
Worker Threads - не панацея. Overhead на спавн V8 (50-100мс), копирование данных, GC на shared memory. На мелких задачах (<100мс) - хуже sync. В кластере с pm2 - следи за nested threads, иначе 70% CPU на double-marking.
Остаётся подумать над гибридами: кластер + workers только для пиковых нагрузок. Или мигрируй на Bun/ Deno с native многопоточностью - но это уже другой подкапот. Тестируй под нагрузкой: 10GB CSV + 100 RPS API, увидишь реальные цифры.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.