비동기가 가능한데도 Node.js는 싱글스레드?! - 42-Gang/project-wiki GitHub Wiki
1. Node는 브라우저에서 시작되었다
Node.js는 원래 웹 브라우저에서 실행되던 자바스크립트(JavaScript)를 서버에서도 사용할 수 있게 만든 런타임이다. 자바스크립트는 브라우저의 UI 스레드를 공유하는 언어였기 때문에 기본적으로 싱글 스레드로 동작하도록 설계되었다. Node.js는 이러한 특성을 그대로 가져와 싱글 스레드 기반의 이벤트 루프 구조를 채택했다. 이 구조는 복잡한 동기화나 스레드 경쟁 없이도 높은 동시성을 확보할 수 있는 장점이 있다.
2. 어떻게 동시에 싱글 스레드로 수많은 요청을 처리할 수 있을까?
Node.js는 내부적으로 이벤트 루프(Event Loop) 를 기반으로 작동하며, 실제 입출력(I/O) 작업은 libuv라는 C 기반의 라이브러리를 통해 처리된다. 즉, 파일 시스템 접근, 네트워크 통신, 데이터베이스 질의 등 시간이 오래 걸리는 작업은 운영체제 또는 libuv의 스레드 풀에 위임된다.
Node.js의 메인 스레드는 이러한 작업을 직접 기다리지 않고, 완료되면 콜백을 받아 처리한다. 덕분에 단일 스레드임에도 동시에 수천 개의 연결을 처리할 수 있다. 이 구조는 비동기 방식에 최적화된 형태로, 서버 자원의 낭비 없이 효율적인 처리가 가능하다.
3. Event Queue, Microtask Queue, Callback Queue란?
자바스크립트의 이벤트 루프는 다양한 큐를 통해 작업을 관리한다.
- Microtask Queue: Promise.then, async/await의 후속 작업, process.nextTick 등이 들어간다. 이벤트 루프가 다음 단계로 넘어가기 전에 이 큐의 작업은 모두 처리된다.
- Callback Queue (Task Queue): setTimeout, setInterval, I/O 콜백 등 일반적인 비동기 작업의 콜백이 들어간다.
- Event Queue 는 개념적으로 callback queue를 포함하는 더 큰 큐 시스템을 말하기도 하며, 실제로는 여러 종류의 큐를 이벤트 루프가 순서대로 순회하며 처리한다.
이벤트 루프는 한 사이클마다 먼저 콜 스택이 비었는지 확인하고, 그다음 Microtask Queue → Callback Queue 순으로 작업을 처리한다.
이 처리과정을 눈으로 확인해볼 수 있는 JS Visualizer사이트가 존재한다. 한번 꼭 확인해보자!
4. 그렇다면 Node.js는 어떤 경우에 유리할까? (Spring과의 비교)
구분 | Node.js | Spring (Java) |
---|---|---|
스레드 모델 | 싱글 스레드 + 비동기 | 멀티 스레드 (스레드 풀) |
장점 | 경량, 빠른 응답, 낮은 메모리 소비, 단순한 구조 | CPU 작업에 강함, 복잡한 로직 처리 유리 |
단점 | CPU 바운드 작업에 약함, 동기 코드 남발 시 성능 저하 | 스레드 수가 많아지면 오버헤드 발생 |
적합한 상황 | 많은 I/O 요청, 채팅, API 서버 등 | 복잡한 비즈니스 로직, 트랜잭션 중심 서비스 |
Node.js는 I/O 바운드 중심의 애플리케이션에 유리하다. 반면, Spring은 스레드를 활용하여 복잡한 계산이나 동시 처리 로직을 안정적으로 수행할 수 있다.
결론
Node.js는 싱글 스레드이지만, 비동기 이벤트 루프 구조 덕분에 수많은 요청을 처리할 수 있다. 이는 스레드를 요청마다 생성하거나 점유하지 않기 때문에 메모리 사용이 적고, 요청 대기 시간이 짧다. 이런 구조 덕분에 Node.js는 높은 동시성을 요구하는 웹 서버에 적합한 선택이 된다.