Files
2nd/10_Wiki/Topics/Coding/Node_Worker_ChildProcess.md
T
2026-05-09 21:08:02 +09:00

5.1 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
node-worker-childprocess Node Worker / Child Process — CPU 작업 / 격리 Coding draft B conceptual 2026-05-09 2026-05-09
node
worker
child-process
cpu
vibe-coding
language applicable_to
TS / Node
Backend
worker_threads
child_process
fork
spawn
MessageChannel
transferList

Worker / Child Process

Node = single-threaded JS. CPU 무거운 작업 = worker_threads (같은 프로세스). 외부 명령 / 격리 = child_process. Cluster 는 다중 server (PM2 같은 거).

📖 핵심 개념

  • worker_threads: 같은 프로세스 다른 thread. 빠른 통신 (SharedArrayBuffer).
  • child_process: 별 프로세스. 격리 강함.
  • spawn / exec / fork: 다른 시작 방식.
  • Tinypool / piscina: worker pool.

💻 코드 패턴

worker_threads (CPU 작업)

// main.ts
import { Worker } from 'node:worker_threads';

const worker = new Worker(new URL('./hashWorker.ts', import.meta.url));
worker.on('message', (h) => console.log('hash:', h));
worker.postMessage({ data: 'hello' });
// hashWorker.ts
import { parentPort } from 'node:worker_threads';
import { createHash } from 'node:crypto';

parentPort!.on('message', ({ data }) => {
  const h = createHash('sha256').update(data).digest('hex');
  parentPort!.postMessage(h);
});

Worker pool (piscina)

import Piscina from 'piscina';

const pool = new Piscina({ filename: new URL('./worker.ts', import.meta.url).href });

// usage
const result = await pool.run({ data: 'x' });
const results = await Promise.all(items.map(i => pool.run(i)));

Transfer (zero-copy ArrayBuffer)

const buf = new ArrayBuffer(10_000_000);
worker.postMessage({ buf }, [buf]);
// buf 는 main 에서 더 사용 못 함 — transferred

child_process.spawn (외부 명령, streaming)

import { spawn } from 'node:child_process';

const ff = spawn('ffmpeg', ['-i', 'in.mp4', 'out.mp3']);
ff.stdout.on('data', (d) => log.info(d.toString()));
ff.stderr.on('data', (d) => log.warn(d.toString()));
ff.on('close', (code) => console.log('exit', code));

// async 변환
import { spawn } from 'node:child_process';
import { once } from 'node:events';

async function run(cmd: string, args: string[]): Promise<string> {
  const p = spawn(cmd, args);
  let out = ''; let err = '';
  p.stdout.on('data', (d) => { out += d; });
  p.stderr.on('data', (d) => { err += d; });
  const [code] = await once(p, 'close');
  if (code !== 0) throw new Error(`${cmd} failed: ${err}`);
  return out;
}

child_process.execFile (명령 + arg 안전)

import { execFile } from 'node:child_process';
import { promisify } from 'node:util';

const exec = promisify(execFile);
const { stdout } = await exec('git', ['log', '--oneline', '-10']);

⚠️ exec (shell) 보다 execFile 권장 — shell injection 방지.

child_process.fork (Node script)

import { fork } from 'node:child_process';

const child = fork('./jobRunner.ts');
child.send({ task: 'process' });
child.on('message', (r) => console.log(r));

Cluster (multi-server)

import cluster from 'node:cluster';
import { cpus } from 'node:os';

if (cluster.isPrimary) {
  for (let i = 0; i < cpus().length; i++) cluster.fork();
  cluster.on('exit', (w) => { cluster.fork(); }); // 자동 재시작
} else {
  app.listen(8080); // 각 worker 가 같은 포트 share
}

PM2 / systemd / pm2-runtime 가 권장.

MessageChannel (worker 끼리 통신)

const { port1, port2 } = new MessageChannel();
worker1.postMessage({ port: port1 }, [port1]);
worker2.postMessage({ port: port2 }, [port2]);
// 두 worker 가 직접 통신

Timeout / kill

const p = spawn('long-task');
const t = setTimeout(() => p.kill('SIGTERM'), 30_000);
p.on('close', () => clearTimeout(t));

CPU 측정 (어느 게 worker 필요?)

const start = process.hrtime.bigint();
heavyWork();
const ms = Number(process.hrtime.bigint() - start) / 1_000_000;
if (ms > 50) // 50ms 이상 = event loop block — worker 로

🤔 의사결정 기준

작업 추천
외부 CLI (ffmpeg, git) spawn / execFile
CPU heavy JS (압축, hash, ML) worker_threads / piscina
격리 / 별 메모리 child_process.fork
멀티 서버 process cluster + PM2
Quick command + small output execFile (promise)
Long stream spawn

안티패턴

  • exec(userInput): shell injection. execFile + arg 분리.
  • Worker 매번 새로: 시작 비용 큼. pool.
  • postMessage 큰 객체 (deep clone): 느림. transferList.
  • Timeout 없음 + child hang: 영원.
  • stderr 무시: 디버깅 어려움.
  • on('exit') 만 + 데이터 손실: 'close' 가 stdio 다 닫힌 후.
  • EventEmitter listener 누수: 매 spawn 마다 listener 추가.

🤖 LLM 활용 힌트

  • CPU = piscina worker pool.
  • 외부 명령 = execFile (안전).
  • 큰 데이터 = transferList ArrayBuffer.

🔗 관련 문서