1. 워커 스레드(worker threads) 활용
Node.js v10.5.0부터 도입된 워커 스레드는 CPU-집약적 작업을 병렬로 처리할 수 있게 해줍니다.
const { Worker, isMainThread, parentPort } = require('worker_threads');
if (isMainThread) {
const worker = new Worker(__filename);
worker.on('message', (result) => {
console.log('Result from worker:', result);
});
worker.postMessage(100); // 계산할 피보나치 수열의 항 번호
} else {
parentPort.on('message', (n) => {
const result = fibonacci(n);
parentPort.postMessage(result);
});
}
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
설명: 워커 스레드를 사용하면 메인 스레드를 차단하지 않고 CPU-집약적인 작업을 별도의 스레드에서 실행할 수 있습니다. 이는 애플리케이션의 전반적인 응답성을 유지하는 데 도움이 됩니다.
2. 클러스터 모듈 사용
Node.js의 클러스터 모듈을 사용하면 여러 프로세스에 작업을 분산시킬 수 있습니다.
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
console.log(`마스터 ${process.pid} 실행 중`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker, code, signal) => {
console.log(`워커 ${worker.process.pid} 종료`);
});
} else {
http.createServer((req, res) => {
if (req.url === '/compute') {
const result = heavyComputation();
res.writeHead(200);
res.end(`결과: ${result}`);
} else {
res.writeHead(200);
res.end('Hello World');
}
}).listen(8000);
console.log(`워커 ${process.pid} 시작`);
}
function heavyComputation() {
// CPU-집약적인 작업 시뮬레이션
let result = 0;
for (let i = 0; i < 1e9; i++) {
result += i;
}
return result;
}
설명: 클러스터 모듈을 사용하면 여러 CPU 코어를 활용하여 작업을 병렬로 처리할 수 있습니다. 이는 특히 웹 서버의 성능을 크게 향상시킬 수 있습니다.
3. 자식 프로세스 생성
child_process 모듈을 사용하여 별도의 프로세스에서 CPU-집약적 작업을 실행할 수 있습니다.
const { exec } = require('child_process');
function runCPUIntensiveTask(data) {
return new Promise((resolve, reject) => {
exec(`node cpu-intensive-task.js ${data}`, (error, stdout, stderr) => {
if (error) {
reject(error);
return;
}
resolve(stdout.trim());
});
});
}
// 사용 예
runCPUIntensiveTask('someData')
.then(result => console.log('결과:', result))
.catch(error => console.error('에러:', error));
설명: 자식 프로세스를 사용하면 메인 Node.js 프로세스와는 별도로 CPU-집약적 작업을 실행할 수 있습니다. 이 방법은 특히 외부 스크립트나 명령을 실행해야 할 때 유용합니다.
4. 비동기 처리 최적화
CPU-집약적 작업을 작은 단위로 나누어 비동기적으로 처리할 수 있습니다.
function processLargeArray(array, batchSize = 1000) {
return new Promise((resolve, reject) => {
const results = [];
let index = 0;
function processBatch() {
const batch = array.slice(index, index + batchSize);
batch.forEach(item => {
// CPU-집약적인 작업 수행
results.push(heavyComputation(item));
});
index += batchSize;
if (index < array.length) {
setImmediate(processBatch);
} else {
resolve(results);
}
}
processBatch();
});
}
function heavyComputation(item) {
// CPU-집약적인 작업 시뮬레이션
let result = 0;
for (let i = 0; i < 1e6; i++) {
result += Math.sqrt(item * i);
}
return result;
}
// 사용 예
const largeArray = Array.from({ length: 1e5 }, (_, i) => i);
processLargeArray(largeArray)
.then(results => console.log('처리 완료:', results.length))
.catch(error => console.error('에러:', error));
설명: 대규모 작업을 작은 단위로 나누어 비동기적으로 처리함으로써, 이벤트 루프의 차단을 최소화하고 애플리케이션의 응답성을 유지할 수 있습니다.
결론
Node.js에서 CPU-집약적 작업을 효율적으로 처리하기 위해서는 워커 스레드, 클러스터 모듈, 자식 프로세스, 그리고 비동기 처리 최적화 등 다양한 전략을 활용할 수 있습니다. 각 방법은 상황에 따라 장단점이 있으므로, 애플리케이션의 요구사항과 처리해야 할 작업의 특성을 고려하여 적절한 전략을 선택해야 합니다. 또한, 이러한 기법들을 조합하여 사용하면 더욱 효과적인 성능 최적화를 달성할 수 있습니다. CPU-집약적 작업을 처리할 때는 항상 애플리케이션의 전반적인 성능과 응답성을 고려하고, 필요에 따라 전략을 조정하는 것이 중요합니다.