[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user