Node.js 가 어떤식으로 외부 I/O 작업이 동작하는지 어렴풋이 알고 있었는데 글을 쓰면서 정리를 하고 싶어 글을 작성하게 되었습니다. Node.js는 단일 쓰레드로 동작하지만, 효율적인 비동기 I/O 처리를 통해 대규모 동시성을 지원하는 서버 환경을 제공합니다. 본 글에서는 Node.js의 단일 쓰레드 모델이 어떻게 다양한 I/O 작업(DB, 파일, 네트워크 등)을 처리하며, 하드웨어 쓰레드의 활용을 통해 성능을 극대화하는지 구체적으로 알아보겠습니다.
Node.js의 이벤트 루프는 단일 쓰레드에서 실행됩니다. 이는 코드 실행, 이벤트 처리, 콜백 함수 호출 등이 하나의 쓰레드에서 순차적으로 이루어진다는 것을 의미합니다.
Race Condition, Deadlock 같은 문제를 방지.Node.js는 단일 쓰레드에서 비동기 I/O를 활용하여 효율적인 처리를 수행합니다. 이를 가능하게 하는 핵심은 이벤트 루프(Event Loop)와 백그라운드 스레드(pool)입니다.
이벤트 루프는 Node.js의 핵심으로, 다음과 같은 과정을 통해 비동기 작업을 처리합니다:
이 과정에서 Node.js의 메인 쓰레드는 계속 이벤트 루프를 돌며 새로운 요청을 처리합니다.
javascript
const mysql = require('mysql2/promise');
(async () => {
const connection = await mysql.createConnection({host: 'localhost', user: 'root', database: 'test'});
// DB 쿼리 비동기 처리const [rows] = await connection.execute('SELECT * FROM users');
console.log(rows);// 쿼리 결과는 이벤트 루프가 아닌 백그라운드에서 처리
})();
Node.js의 fs 모듈은 파일 읽기/쓰기를 비동기로 처리합니다. 이는 OS의 비동기 파일 API를 활용하거나, libuv 스레드 풀을 통해 동작합니다.
javascript
const fs = require('fs');
fs.readFile('example.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log(data);// 파일 읽기는 libuv의 스레드 풀에서 처리
});
Node.js의 네트워크 요청은 OS 커널의 네이티브 I/O 인터페이스를 활용해 처리됩니다. 이를 통해 효율적으로 다수의 요청을 동시에 처리할 수 있습니다.
Node.js는 단일 쓰레드로 동작하지만, libuv를 통해 하드웨어 스레드를 효과적으로 활용합니다. libuv는 Node.js에서 비동기 I/O 작업을 처리하는 C 라이브러리로, 다음과 같은 역할을 합니다:
UV_THREADPOOL_SIZE=8Node.js는 이러한 단점을 보완하기 위해 단일 쓰레드를 유지하면서, libuv 및 OS의 기능을 통해 멀티 쓰레드의 이점을 활용합니다.
Node.js는 기본적으로 CPU 집약적인 작업에는 적합하지 않습니다. 이런 경우 워커 스레드(Worker Threads)를 활용할 수 있습니다.
javascript
const { Worker } = require('worker_threads');
const worker = new Worker('./worker.js');
worker.on('message', (result) => {
console.log('Result:', result);// 워커 스레드에서 결과 수신
});
Node.js는 클러스터링을 통해 단일 프로세스를 여러 CPU 코어에 분산할 수 있습니다.
javascript
const cluster = require('cluster');
if (cluster.isMaster) {
for (let i = 0; i < require('os').cpus().length; i++) {
cluster.fork();
}
} else {
require('./server.js');// 각 프로세스가 서버 역할 수행
}
Node.js의 단일 쓰레드는 단순하고 효율적인 이벤트 기반 모델로, 대규모 동시성을 지원하는데 탁월한 성능을 발휘합니다. 비록 단일 쓰레드로 동작하지만, libuv를 통해 하드웨어 스레드를 활용하여 다양한 I/O 작업을 처리함으로써 멀티 쓰레드의 이점을 효율적으로 사용합니다.
Node.js의 구조와 처리 방식을 이해하면, 특정 상황에 적합한 설계를 통해 최대의 성능을 발휘할 수 있습니다.