---
id: node-worker-childprocess
title: Node Worker / Child Process — CPU 작업 / 격리
category: Coding
status: draft
source_trust_level: B
verification_status: conceptual
created_at: 2026-05-09
updated_at: 2026-05-09
tags: [node, worker, child-process, cpu, vibe-coding]
tech_stack: { language: "TS / Node", applicable_to: ["Backend"] }
applied_in: []
aliases: [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 작업)
```ts
// 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' });
```

```ts
// 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)
```ts
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)
```ts
const buf = new ArrayBuffer(10_000_000);
worker.postMessage({ buf }, [buf]);
// buf 는 main 에서 더 사용 못 함 — transferred
```

### child_process.spawn (외부 명령, streaming)
```ts
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 안전)
```ts
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)
```ts
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)
```ts
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 끼리 통신)
```ts
const { port1, port2 } = new MessageChannel();
worker1.postMessage({ port: port1 }, [port1]);
worker2.postMessage({ port: port2 }, [port2]);
// 두 worker 가 직접 통신
```

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

### CPU 측정 (어느 게 worker 필요?)
```ts
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.

## 🔗 관련 문서
- [[Node_Streams_Patterns]]
- [[Backend_Job_Queue_Patterns]]
- [[AI_Code_Interpreter_Sandbox]]
