5.1 KiB
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 |
|
|
|
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.