Почему Node.js кластеры жрут 70% CPU на GC: утечки worker_threads и нативный фикс
-
Node.js кластеры на многозадачных серверах часто показывают 70% CPU в GC. Проблема не в V8, а в том, как worker_threads делят память и объекты между процессами. Разберем, почему это происходит и как починить без pm2 - чисто нативно.
Это спасет от просадок производительности под нагрузкой. Узнаете скрытые утечки и получите код, который реально работает. Без костылей и лишних зависимостей.
Как worker_threads ломают GC в кластере
В кластере Node.js основной процесс форкает воркеры по числу ядер. Каждый воркер - отдельный V8 изолят с собственной heap. Но когда добавляешь worker_threads внутри воркера, начинается бардак: потоки шейрят SharedArrayBuffer и Atomics, а GC не знает, что с этим делать.Под нагрузкой объекты мигрируют между потоками, создавая кросс-трэдовые ссылки. V8 маркит их дважды - в каждом изоляте, и GC взлетает на 70% CPU.
Реальный пример: парсер JSON в воркере с worker_thread для тяжелых вычислений. SharedArrayBuffer заполняется данными, но без правильного cleanup ссылки висят. HTOP покажет, как GC жрет все ядра, пока основной event loop стоит.Кластер думает, что все ок - метрики лгут, а отклик растет до таймаутов. Логично перейти к симптомам.
- Double-marking объектов: SharedArrayBuffer делает GC в каждом потоке независимым, но ссылки дублируются - overhead x2.
- Утечки в message ports: PostMessage между threads копирует объекты, не уничтожая оригиналы - heap растет.
- Atomics.wait() блокирует GC: Синхронизация тормозит minor GC, major запускается чаще.
Симптом Норма Проблема GC CPU <20% 70%+ Heap size stable растет Throughput 10k req/s 2k req/s Скрытые утечки: где именно они прячутся
Основная засада - в SharedArrayBuffer без детектора ownership. Когда thread A пишет в буфер, thread B держит WeakRef, но V8 не видит, что буфер больше не active. Результат: phantom references множатся, minor GC не справляется.Добавь кластер - и каждый воркер плодит свои утечки независимо.
Пример кода-убийцы: воркер спавнит threads для батч-обработки, шлет данные через parentPort.postMessage({data: hugeArray}). Array не transferable - копируется целиком, heap x10. Под 1k RPS кластер умирает за 5 минут.Переходим к диагностике.
node --inspect+ Chrome DevTools: смотри heap snapshots в worker_threads.clinic.js flameна кластере: увидишь GC пики синхронно по всем воркерам.process.memoryUsage()в setInterval: детект growth в rss и heapTotal.
Нюанс: worker.terminate() не всегда чистит SharedArrayBuffer - detach вручную.
Нативный фикс без pm2: чистый кластер + threads
Фикс простой: кастомный кластер-менеджер с explicit cleanup. Используй process.fork() вместо cluster, управляй threads lifecycle сам. В воркере - Transferable objects для postMessage, WeakMap для shared refs.Код-скелет: в master спавни воркеры, в них - threads только для CPU задач. На exit - Atomics.store(buffer, 0, 0); detachArrayBuffer(). GC падает до 10%.Нет pm2 - нет оверхеда на sticky sessions и respawn.
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads'); if (isMainThread) { const worker = new Worker(__filename); worker.postMessage({ task: 'heavy' }, [sharedBuffer]); worker.on('exit', () => { sharedBuffer = null; // explicit cleanup }); } else { // process task, post back transferable parentPort.postMessage(result, [resultBuffer]); }До фикса После GC 70% 10% Mem leak stable RPS x5 growth Проверка и тюнинг: что мониторить дальше
Запусти под нагрузкой Artillery или autocannon - смотри p95 latency. Тюнь --max-old-space-size=4g на воркер, ParallelGCThreads=ядер/2.Если кластер на 8 cores - 4 threads max, остальное I/O.
Кластер стабилен, но подумай о numactl для pinning threads к cores - еще -20% GC. Или мигрируй тяжелые задачи в Rust WASM - но это уже другая история. Меньше threads - чище heap.
Здравствуйте! Похоже, вас заинтересовала эта беседа, но у вас ещё нет аккаунта.
Надоело каждый раз пролистывать одни и те же посты? Зарегистрировав аккаунт, вы всегда будете возвращаться на ту же страницу, где были раньше, и сможете выбирать, получать ли уведомления о новых ответах (по электронной почте или в виде push-уведомлений). Вы также сможете сохранять закладки и ставить лайки постам, чтобы выразить свою благодарность другим участникам сообщества.
С вашими комментариями этот пост мог бы стать ещё лучше 💗
Зарегистрироваться Войти© 2024 - 2026 ExLends, Inc. Все права защищены.