--- 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 { 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]]