Node.js란?
Node.js는 자바스크립트를 브라우저 밖, 즉 서버에서도 실행할 수 있게 해주는 런타임입니다. 프론트엔드 개발자가 백엔드 영역까지 확장할 수 있게 해주었고, 빠른 비동기 처리를 기반으로 대규모 실시간 시스템에서 자주 활용됩니다.
정리하자면, Node.js는 Chrome V8 JavaScript 엔진 위에서 동작하는 JavaScript 런타임입니다.
(쉽게 말하면, 브라우저 없이도 JavaScript 코드를 실행할 수 있게 해주는 환경입니다.)
- 최초 릴리즈: 2009년
- 개발자: Ryan Dahl
- 주요 특징:
- 이벤트 기반
- 논블로킹 I/O
- 싱글 스레드 처리
Node.js의 동작 원리
1. V8 엔진
Node.js는 Google Chrome의 V8 엔진 위에서 동작합니다. 이 엔진은 JavaScript 코드를 기계어로 바로 컴파일하여 빠르게 실행합니다.
- JavaScript → V8 → 머신코드 → 실행
- 고성능 처리를 위한 핵심 요소 중 하나
2. 이벤트 기반 처리
Node.js는 I/O 작업을 처리할 때 이벤트 기반(Event-driven) 구조를 채택합니다.
fs.readFile('test.txt', (err, data) => {
if (err) throw err;
console.log(data.toString());
}); console.log('파일 읽기 요청 완료');
readFile은 파일을 읽는 동안 블로킹하지 않고, 바로 다음 줄을 실행한 뒤 완료되면 콜백을 호출합니다.
3. 싱글 스레드 + 논블로킹 I/O
Node.js는 싱글 스레드로 작동하지만, I/O 작업은 내부적으로 스레드 풀(libuv)을 사용해 처리합니다.
(코드는 한 줄씩 순차적으로 실행되지만, I/O 작업은 병렬로 처리돼 결과만 콜백으로 받아옴)
이벤트 루프(Event Loop)
Node.js가 싱글 스레드임에도 동시성을 구현하는 핵심 기술은 이벤트 루프입니다.
이벤트 루프는 아래처럼 동작합니다.
- 호출 스택(Call Stack)
- 백그라운드(파일 시스템, 네트워크 등)
- 태스크 큐(Callback Queue)
- 이벤트 루프가 태스크 큐를 확인하고 스택이 비면 콜백 실행
추가적으로 핵심기술에 대해 조금 더 디테일하게 알아보겠습니다.
V8 JavaScript Engine – 초고속 JS 실행의 비결
V8은 Google이 개발한 오픈소스 JavaScript 엔진으로, Chrome과 Node.js에서 모두 사용됩니다.
특징:
- Just-In-Time (JIT) 컴파일: JS 코드를 실행 중에 기계어로 변환하여 속도 향상
- 메모리 관리: 가비지 컬렉션과 힙 관리를 통해 안정적인 실행
- 빠른 실행: JS를 바이트코드가 아닌, 바로 네이티브 코드로 실행
-> V8은 JavaScript를 해석하지 않고, 바로 실행 가능한 네이티브 코드로 바꿔 빠르게 처리
libuv – Node.js의 I/O 마스터
Node.js의 비동기성과 이벤트 기반 I/O는 libuv라는 C 기반 라이브러리 덕분입니다.
libuv의 역할:
- 비동기 파일 시스템 접근
- 네트워크 소켓 처리 (TCP/UDP)
- 스레드 풀 (Thread Pool) 관리
- 이벤트 루프와 통신하여 작업 완료 통지
구조 요약:
- I/O 요청이 들어오면 libuv가 OS 레벨 비동기 API 또는 스레드 풀을 통해 처리
- 작업 완료 시 Node.js로 콜백 알림
Ex) fs.readFile()을 호출하면, 실제로는 libuv가 백그라운드에서 처리한 후 콜백을 호출해주는 구조
싱글 스레드지만 멀티태스킹 – 이벤트 루프의 위엄
Node.js는 JavaScript 실행 자체는 싱글 스레드입니다. 하지만 동시에 수천 개의 요청을 처리할 수 있습니다. 비밀은 바로 이벤트 루프(Event Loop) 입니다.
이벤트 루프 구조:
- Call Stack: JS 코드가 실행되는 공간 (순차적 실행)
- Task Queue: 완료된 비동기 작업의 콜백이 쌓이는 큐
- Event Loop: Call Stack이 비면 Task Queue의 콜백을 가져와 실행
- libuv: 타이머, I/O, OS 작업들을 Event Loop와 연결
예시
console.log("Start");
setTimeout(() => {
console.log("Timer Done");
}, 1000);
console.log("End");
// 출력 결과: // Start // End // Timer Done
-> setTimeout의 콜백은 libuv의 타이머에서 대기하다 이벤트 루프에 의해 실행됨
비동기 처리 방식 – Non-blocking I/O
Node.js는 논블로킹이라는 I/O 모델을 사용합니다. 이는 하나의 요청이 끝날 때까지 기다리지 않고, 그 사이 다른 요청을 처리할 수 있게 합니다.
예시
const fs = require('fs');
fs.readFile('file.txt', 'utf8', (err, data) =>
{ console.log(data);
});
console.log("파일 읽는 중...");
파일이 완전히 읽히기 전에 "파일 읽는 중..."이 먼저 출력됨
중요한 이유
- 동시 사용자 수가 많은 서버에 유리
- 블로킹 없이 네트워크, DB, 파일 등을 효율적으로 처리 가능