Node.js는 서버 사이드 자바스크립트 런타임 환경입니다.

런타임 환경은 프로그래밍 언어가 구동되는 환경입니다. 자바스크립트가 브라우저에서 실행되기에 우리는 웹 서비스를 이용할 수 있죠. 이때의 자바스크립트 런타임 환경은 브라우저라고 할 수 있습니다. 브라우저에서 자바스크립트라는 프로그래밍 언어가 구동되기 때문입니다. 브라우저는 클라이언트 사이드 자바스크립트 런타임 환경이라고 볼 수 있겠네요.
Node.js는 자바스크립트 코드를 브라우저 밖에서 실행할 수 있게 해주는 런타임 환경입니다. 자바스크립트라는 프론트엔드 필수 언어로 백엔드까지 작성할 수 있다는 엄청난 장점 덕분에 높은 점유율을 확보할 수 있었죠. Node.js는 오픈 소스 자바스크립트 엔진인 크롬 V8에, 비동기 이벤트 처리 라이브러리인 libuv를 결합하여 구현되었습니다.
Node.js는 계층 구조로 설계되어, 하단 계층에 있는 API를 사용합니다. 예시를 들어보겠습니다.

- 애플리케이션 - fs.readFile('data.txt', 'utf8', callback) 코드 작성
- Node.js API - fs 모듈이 readFile 함수를 JavaScript로 제공
- Node.js 바인딩 - JavaScript의 readFile 호출을 C++ 파일 읽기 요청으로 변환
- Node.js 표준 라이브러리 - C++로 파일 읽기 로직 처리
- libuv - 비동기로 파일 읽기 작업을 스레드풀에 요청하고 완료되면 콜백 실행
- 저수준 라이브러리 - 운영체제의 파일 시스템 API 호출 (예: read 시스템 콜)
- 운영체제 - 실제 디스크에서 'data.txt' 파일 데이터를 읽어서 반환
V8 엔진은 사용자가 작성한 자바스크립트 코드를 기계어로 변환하여 실행하는 역할을 합니다. 기계어로 변환하는 과정을 컴파일이라고 합니다.

- Source: 개발자가 작성한 JavaScript 원본 코드입니다.
- Parser: 원본 코드를 V8 엔진이 이해할 수 있는 토큰으로 분해하고 분석합니다. AST(Abstract Syntax Tree)를 생성합니다.
- Ignition: AST를 Bytecode라는 중간 단계의 코드를 생성합니다. Bytecode는 인터프리터(Ignition)에 의해 바로 실행될 수 있습니다.
- SparkPlug: 코드를 빠르게 실행하기 위해, Bytecode를 최적화되지 않은(Non-Optimized) 기계 코드로 신속하게 컴파일합니다.
- TurboFan: 코드가 반복적으로 실행되면서 어떤 부분이 자주 사용되는지 파악합니다. 자주 사용되는 코드만 골라서 매우 효율적인, 고도로 최적화된(Optimized) 기계 코드로 컴파일할 수 있습니다.
- Deoptimize: 만약 TurboFan이 최적화한 코드의 가정이 틀어지면, V8은 최적화된 기계 코드를 버리고 다시 SparkPlug가 만든 비최적화 코드나 Ignition의 Bytecode로 돌아가서 실행을 이어갑니다.
libuv를 통해 이벤트 루프와 운영체제 시스템 API를 사용할 수 있습니다. 핵심은 이벤트 루프입니다.

- 이벤트 루프는 여러 개의 FIFO Queue로 이루어져 있습니다.
- Timer 단계는 setTimeout(), setInterval()을 처리합니다.
- Pending 단계는 다음 반복으로 연기된 콜백을 처리합니다.
- Idle / Prepare 단계는 내부적으로만 사용합니다.
- Poll 단계는 소켓 연결, 파일 읽기 등의 작업을 수행합니다.
- Check 단계는 setImmediate()를 처리합니다.
- Close 단계는 콜백의 종료 처리를 합니다.
- microTaskQueue와 nextTickQueue에 있는 작업은 각 단계마다 우선적으로 실행합니다.(nextTickQueue의 작업의 우선순위가 microTaskQueue 작업의 우선순위보다 높습니다.)

- 점선은 libuv 영역을 의미합니다.
- 애플리케이션에서 요청이 발생합니다. V8 엔진은 자바스크립트 코드를 바이트 코드나 기계어로 변환합니다.
- 자바스크립트로 작성된 Node.js의 API는 C++로 작성된 코드를 사용합니다.
- V8 엔진은 이벤트 루프로 libuv를 사용하고, 전달된 요청을 libuv 내부의 이벤트 큐에 추가합니다.
- 이벤트 큐에 쌓인 요청은 이벤트 루프에 전달되고, 운영체제 커널에 비동기 처리를 맡깁니다. 운영체제 내부적으로 비동기 처리가 힘든 경우는 워커 스레드에서 처리합니다.
- 운영체제의 커널 또는 워크 스레드가 완료한 작업은 다시 이벤트 루프로 전달됩니다.
- 이벤트 루프에서는 콜백으로 전달된 요청에 대한 완료 처리를 하고 넘깁니다.
- 완료 처리된 응답을 애플리케이션으로 전달합니다.