Node.js 런타임 내부 동작
1Runtime Enviroment----------------------------------------23v8 ---> Node.js API ---> Node.js bindings ---> libuv45^ V8 : js코드 실행6^ libuv : db접근, 파일 읽디 등 v8이 다루는 것 이외에는 libuv가 처리7^ Node.js API : 파일 시스템이나, crypto 암호화 처리 등의 API8: API는 JS로 작성된 것도 있지만, C++나 C로 되어있음9----------------------------------------------------------
1. 인터넷에 어떤 파일을 다운받으면?
1Node.js ---> JS로 다운 요청 보냄 ---> Node.js Http module API 사용 --->2Node.js Binding을 통해 libuv로 할일 전달 --->3libuv를 통해서 파일을 다운로드 (이 시간 동안에 자바스크립트는 다른 일을 할 수 있음) --->4window, mac, Linux 파일 시스템 같은걸 처리하는 방법이 다 다른데 libuv가 이런 다른 부분도 핸들링해줌 --->5작업이 끝났다면 node.js가 가져옴
2. Node.js 오픈소스 보기
- https://github.com/nodejs/node
- https://github.com/nodejs/node/tree/main/lib
- Node.js API의 JavaScript 부분(console, http, os, path, process)
- 각각의 파일들은 Node.js 공식문서에 있는 Node.js API를 정의함
- https://github.com/nodejs/node/tree/main/src
- Node.js의 Binding처리를 해주는 C++ 부분
- JavaScript와 C++을 연결해줌
- 만약 어떤 파일을 열고자 한다면?
fs.open()API를 사용
3. libuv
- https://libuv.org/
- https://github.com/libuv/libuv
- 비동기 input과 output에 포커스를 둔 멀티 플랫폼(Window, Mac, Linux) 지원 라이브러리
- 이벤트 루프를 기반으로 하는 비동기 I/O에 대한 지원을 제공하는 다중 플랫폼 C 라이브러리
- 주로 Node.js에서 사용하도록 개발되었지만, Julia, Luvit, pyuv 등과 같은 다른 도구에서도 사용됨
- 각 OS별로 가장 빠른 비동기 IO 인터페이스로 통일된 코드로 돌릴 수 있는 장점이 존재
- https://github.com/libuv/libuv/tree/v1.x/src
- unix -> mac, linux 처리
- win -> window 처리
- 해당 라이브러리는 파일 시스템, DNS, 네트워크, 파이프, 신호처리, 폴링 및 스트리밍을 처리하는 메커니즘을 제공
- cf.
IO는 input과 output의 약어로 입력과 출력을 간단히 줄여서, 입출력이라 함- 입출력은 컴퓨터 내부 또는 외부의 장치와 프로그램 간의 데이터를 주고받는 것을 말함
원래 UNIX와 Window간의 파일 컨트롤 방법이 다르지만, 이런 식으로 libuv에서 서로 compatible하게 해주므로, 노드를 사용하는 개발자는 따로 신경쓰지 않고, 사용 OS에서 같은 방법으로 사용할 수 있다.
4. 비동기와 동기
동기(synchronous, 시간을 맞춤)- 대학생 되는 법
- 고등학교 1학년
- 고등학교 2학년
- 고등학교 3학년
- 대학교 가기
- 대학생 되는 법
비동기(asynchronous, 시간을 맞추지 않음)- 취직하는 법
- 토익 공부
- 자격증 취득
- 영어 공부
- 취업
- 취직하는 법

동기는 먼저 이전의 것이 끝나야 다음 것을 할 수 있지만, 비동기는 1번을 하면서 2번을 할 수 있고, 3, 4번도 할 수 있다.
JS의 문법 중에 async, await 문법을 사용하는 이유는 비동기 요청인데 동기처럼 사용하기 위해서 이다.
첫번쨰 요청이 끝나야지, 다음 2번쨰 요청을 하기 위해서이다.
4.1 코드로 보는 async & sync
4.1.1 동기
1console.log(1)2console.log(2)34/** 결과5* 16* 27*/
4.1.2 비동기
1setTimeout(() => {2console.log(1)3}, 1000)4console.log(2)56/** 결과7* 28* 19*/
5. Node.js에서 비동기를 주로 사용
- Node.js에서 비동기를 주로 사용
- DB에서 데이터를 읽을 때, 저장할 때, 지울 때 등 대부분의 요청이 비동기로 이뤄짐
- 대부분의 작업이 어느 정도의 시간을 요구하기 때문이다.
- 이 덕분에 여러 개의 작업을 해도 다른 작업을 기다리지 않고,
- 빠르게 여러 작업을 처리할 수 있습니다.
6. JS는 동기 언어
1setTimeout(() => {2console.log(1)3}, 1000)4console.log(2)
- JS는 한 줄 실행하고 또 다음 줄 실행하는 동기 언어
- 하지만 생각해보면 콜백 함수를 실행하는 비동기 코드를 자주 사용했음
- 어떻게 된걸까?
비동기 코드를 작성하기 위해서 JS 이외의 도움을 받는다!
- 위에 setTimeout도 보면 사실 JS의 부분이 아닙니다.
- 브라우저에서 사용을 한다면, 브라우저 API를 사용하는 것이며 (window object),
- Node에서 사용한다면 Node API를 사용하는 것이다. (global object)
결국, JS는 동기적인 언어이지만, 다른 API의 도움을 받아서 비동기처럼 사용할 수 있다.
7. Node.js의 특징을 이용한 예시
The most significant difference between Node.js and PHP is that most functions in PHP block until completion (commands execute only after previous commands finish), while Node.js functions are non-blocking (commands execute concurrently or even in parallel, and use callbacks to signal completion or failure).
- wikipedia
- 쓰레드 === 사람
- 논블로킹 === 자동화 기계
첫 번쨰 예시
- 스레드(Thread)라는 사람이 라면집 개업
- 혼자 장사함
- 손님이 주문하면, 요리를 다 할 떄까지 주문을 못받음
두 번째 예시
- 여러 명의 직원(Thread)을 뽑음
- 장사가 안되면, 직원(Thread)이 놈
세 번쨰 예시
- 첫 번쨰 예시에서 스레드(Thread)가
- 논블로킹(Non-blocking) 기계를 샀음
- 논블로킹 기계가 손님 주문을 받음
- 스레드는 요리만 함
프로그래밍 언어마다 차이가 있지만, 위 3가지 방식 중 한 가지 형태로 실행됩니다.
- 두 번쨰 예시
- 가지고 있는 자원이 많은 경우
- e.g. Java
- 세 번쨰 예시
- 가지고 있는 자원이 한정적이고 최대한 효율적이여 햐는 경우
- e.g. Node
- Node는 가볍고, 효율적이고, 높은 성능처리를 위해
논블로킹I/O, 싱글 스레드, 이벤트 루프라는 특징을 가집니다.
8. 논블로킹(Non-blocking) I/O
프로그래밍 언어별로 처리하는 방식은 크게 2가지가 있습니다.
- 여러 개의 작업을 동시에 처리할 수 있는 언어
- 한 번에 하나의 작업만 처리할 수 있는 언어 (e.g. JavaScript)
그렇지만 I/O 작업의 경우 JS도 동시처리할 수 있습니다.
- e.g. 파일 읽기/쓰기같은 파일 시스템 처리
- e.g. DB로 데이터를 전송/조회같은 네트워크 요청
cf. I/O 작업 = 입력(input)과 출력(output)의 줄임말
8.1 블로킹(Blocking)
- 함수를 호출하면 호출된 함수가 작업을 마칠 떄까지 제어권을 돌려주지 않고 함수가 종료될 떄까지 대기하는 방식
- 함수가 완전히 종료되기 전까지는 다른 작업을 진행하지 못하고 기다리는 것
- 동기적 I/O
- 파일 쓰기가 완료될 떄까지 프로그램이 다음 코드를 실행하지 않고,
- 대기하기 때문에 파일을 쓰는 동안 CPU가 사용되지 않아 비효율적
8.2 논블로킹(Non-blocking)
- 함수를 호출하면 호출된 함수에게 바로 제어권을 넘겨서 다른 작업을 바로 진행할 수 있도록 하는 방식
- 함수를 호출되어 실행되는 중에도 다른 작업을 진행 가능
- 비동기적 I/O
- 파일 쓰기를 요청한 후 다른 작업을 할 수 있어서 좀 더 효율적
“논 블로킹이 좋다, 블로킹이 좋다”의 개념이 아니라 어떤 경우에는 블로킹 작업을 해야하고, 어떤 경우엔 논블로킹 작업을 해야할 떄도 있습니다.
9. 프로세스 및 스레드
9.1 프로세스(Process)
프로세스: 컴퓨터가 카톡같은 프로그램을 통해 어떤 일을 하고 있는 상태를 의미- 2가지 특징
- 프로세서(processors)가 프로세스를 하나씩 조금씩 빠르게 처리해 모든 프로세스를 처리하는 것처럼 보이는
동시성 - 여러개의 프로세서가 여러개의 프로세스를 각각 동시에 처리해주는
병렬성을 같이 이용해서 처리
- 프로세서(processors)가 프로세스를 하나씩 조금씩 빠르게 처리해 모든 프로세스를 처리하는 것처럼 보이는
실행파일을 클릭했을 때, 메모리(RAM) 할당이 이루어지고 이 순간부터 이 프로그램은 ‘프로세스’라 불린다.
1---------------------2| Stack |3---------------------4---------------------5| Heap |6---------------------7---------------------8| Data |9---------------------10---------------------11| Text |12---------------------
RAM의 내부 프로세스를 단순화하면 크게 4가지 영역이 존재한다.
Stack: 메서드/함수 매개변수, 반환 주소 및 지역 변수와 같은 임시 데이터가 있는 곳Heap: 런타임 동안 프로세스에 동적으로 할당된 메모리를 의미Data: 전역 및 정적 변수가 있는 곳Text: Program Counter 값과 프로세서 레지스터의 내용이 나타내는 현재 활동이 포함됨
💡 Heap Overflow, Stack Overflow
- Heap의 영역이 증가하여 Stack 영역을 침범하는 상황 => Heap Overflow
- Stack의 영역이 증가하여 Heap의 영역을 침범하는 상황 => Stack Overflow
- 이러한 상황이 될 때 사용되는 메모리의 자유 영역 또한 존재합니다.
프로세스 메모리의 속도는 Stack > Data > Code > Heap 순으로 빠릅니다.
9.2 쓰레드(Thread)
- 카톡을 실행해놓으면 알림, 메시지도 주고받고, 광고도 나와야 합니다.
- 결국 한 프로세스 내에서 여러가지 작업이 동시에 이뤄져야 합니다.
- 이때 쓰레드를 이용하게 됩니다.
- 즉,
스레드: 프로세스 내에서 일을 처리하는 세부 실행 단위
쓰레드는 자원을 공유한다.
- 하나의 프로세스안에서 쓰레드들은 자원을 공유한다.
- 장점 :
- 카카오톡 프로세스 안에서는 비슷한 일을 할 수 있기 때문에
- 자원을 공유하는게 효율적이다.
- 자원을 공유해서 나오는 단점 :
- 공유하는 자원에 여러개의 쓰레드가 동시에 접근할 때, 에러가 날 수 있다.
- 이러한 경우를 위한 코딩을 하기도 디버깅을 하기도 어렵다.
9.3 싱글 스레드 vs 멀티 스레드
1// 싱글 스레드2Thread -> A작업 -> B작업 -> C작업
(1) 싱글 스레드(Single Thread)
- 정의 : 하나의 프로세스 안에서 스레드를 하나만 사용해, 하나의 작업만 수행
- 즉, 동시에 하나의 작업만 처리할 수 있다는 뜻
- e.g. Node
- JS같은 싱글 스레드 언어는 작성된 프로그램이 순차적으로 실행되고, 여러 작업을 동시처리X
- 장점 : 싱글 스레드의 약점을 극복하기 위해 논블로킹을 사용하여, 파일 쓰기, 네트워크를 통한 DB 처리 등의 작업을 하는 동안 컴퓨터가 쉬지않고, 다른 작업을 하도록 해서 적은 자원으로 효율이 좋음
1// 멀티 스레드2A 스레드 -> A작업3B 스레드 -> B작업4C 스레드 -> C작업
(2) 멀티 스레드(Multi Thread)
- 정의 : 하나의 프로세스 안에서 스레드를 여러 개를 두어서 동시에 여러 작업을 수행
- e.g. Java
- 각각의 스레드가 다른 작업을 할당받아, 프로세스가 병렬적으로 여러 작업을 동시에 수행 가능
- 각각 Stack 영역만 따로 할당받고 Code, Data, Heap 영역은 공유함
- 장점 : 동시에 여러 작업을 수행해 성능이 좋음
- 단점 : 많은 자원이 필요하고, 자원이 제대로 사용되지 않는 경우 비효율적
- 공유하는 자원에서 동시에 접근할 때 신경을 써줘야 함
- 스레드 간에 데이터와 힙 영역을 공유하기 때문에, 변수나 자료 구조에서 겹쳐서 오류남
- 이러한 문제로 동기화 작업이 필요
- 병목 현상이 생겨 성능을 저하 가능성이 존재
- 결국, 멀티 스레딩을 관리하는 것은 쉽지 않다.
10. Node의 비동기 작업 처리과정
- JS는 싱글 스레드가 한 번에 하나의 작업만 처리한다.
- Node.js는 V8 JS 엔진과 libuv, llhttp, c-ares,OpenSSL, zlib라는 라이브러리로 구성된다.
- JS가 가진 싱글 스레드의 약점을 극복하기 위해,
- libuv에서 제공하는
Event Loop를 사용한다.

- 작업이
Call Stack에 들어옴 - 비동기 작업이면,
이벤트 루프가Task Queue로 작업을 잠시 옮겨버림 Call Stack의 작업이 끝나서 비어있으면,이벤트 루프가Task Queue의 작업들이 들어온 순서(queue)대로Call Stack으로 옮겨서 처리
11. 이벤트 루프(Event Loop)
1--------------------------------------------------------------------------2JS -|-> V8엔진 ---> Node.js API ---> Node.js Bindings ---> Event Loop(libuv) -|-> 브라우저3--------------------------------------------------------------------------
- 이벤트 루프는 Node.js가 여러 비동기 작업을 관리하기 위한 구현체.
- (X) =>
console.log("Hello World"): 동기 작업 - (O) =>
file.readFile('file.txt', callback): 비동기 작업 - 이러한 비동기 작업들을 모아서 관리하고 순서대로 실행할 수 있게 해주는 도구.
- cf. https://nodejs.org/en/guides/event-loop-timers-and-nexttick#what-is-the-event-loop
11.1 Event Loop 구조
1┌───────────────────────────┐2┌─>│ timers │ <── setInterval 콜백, setTimeout 콜백3│ └─────────────┬─────────────┘4│ ┌─────────────┴─────────────┐5│ │ pending callbacks │6│ └─────────────┬─────────────┘7│ ┌─────────────┴─────────────┐8│ │ idle, prepare │9│ └─────────────┬─────────────┘ ┌───────────────┐10│ ┌─────────────┴─────────────┐ │ incoming: │11│ │ poll │<─────┤ connections, │ <── make request, db접근12│ └─────────────┬─────────────┘ │ data, etc. │13│ ┌─────────────┴─────────────┐ └───────────────┘14│ │ check │15│ └─────────────┬─────────────┘16│ ┌─────────────┴─────────────┐17└──┤ close callbacks │18└───────────────────────────┘
- 각 박스는 특정 작업을 수행하기 위한
페이즈(Phase)를 의미한다. - 그리고 한 페이즈에서 다음 페이즈로 넘어가는 것을
틱(Tick)이라고 부른다. - 각 단계(Phase)에서는 각각의
큐(queue)가 있다.- e.g. settimeout 함수가 불러지면,
timer 페이즈에 있는 큐에 쌓이게 됩니다. - 그리고 이 큐는 먼저 들어온 게 먼저 나가게 되어있습니다. (First in First Out, FIFO 구조)
- 그리고 싱글 스레드 이기 때문에
timers 페이즈에 있는 일을 끝내거나, 최대 콜백 수가 될 때까지 한 후에다른 단계(페이즈)로 이동한다. - (
timers끝내고 =>pending callback으로 이동)
- e.g. settimeout 함수가 불러지면,
1│ ┌─── 큐 ──────┐2│ ┌─────────────┴─────────────┐ │ │3│ │ poll │<─────┤ A콜백 함수 │ <── B콜백 함수4│ └─────────────┬─────────────┘ │ │5│ └─────────────┘
-
여기서 만약
poll 단계에 왔고, 큐에콜백 함수A하나가 쌓여있었는데,A 콜백 함수안에B콜백 함수가 있다면,A 콜백 함수처리 후B 콜백 함수를poll Queue에 또 추가합니다.- 그런데 Node.js가
poll 단계를 다시 보고, Queue에B콜백 함수가 남아있으니 그것도 처리합니다. - 이렇게 다 처리한 후
다음 단계(페이즈)로 넘어가게 됩니다.
이런 식으로 큐에 너무 많이 쌓이면 노드가 계속 추가되는 작업을 처리하느라 다음 페이즈로 못 넘어갈 것 같지만, 페이즈는 시스템의 실행 한도가 있기에 어느 정도 한도를 넘으면 다른 페이즈로 넘어갑니다.
11.2 Event Loop 각각의 단계
Timer:
- 이벤트 루프의 시작을 알리는 페이즈
setTimeout()및setInterval()에 의해 예약된 콜백을 실행
Pending Callbacks: TCP 오류 유형과 같은 일부 시스템 작업에 대한 콜백을 실행Idle, Prepare
- 내부적으로만 사용됨
- 이 단계에서 이벤트 루프는 아무 작업도 수행하지 않음
- idle 상태이며 Poll 단계로 이동할 준비를 함
Poll
- 대부분의 I/O 관련 콜백을 실행함
- (close 콜백, 타이머에 의해 예약된 콜백 및
setImmediate()을 제외하고 거의 모두).
Check: setImmediate() 콜백이 호출됨Close Callbacks
- 이 단계에서 이벤트 루프는 socket.on(‘close’, fn) 또는 process.exit()와 같은 종료 이벤트와 관련된 콜백을 실행함
- 이벤트 루프의 각 실행 사이에 Node.js는 비동기 I/O 또는 타이머를 기다리고 있는지 확인하고, 없는 경우 완전히 종료함
💡 Timer 심화
setTimeout()및setInterval()의 타이머들의 콜백 큐에 들어가는 것은 아니며,
- 타이머들은
min heap에 들어가 있게 된다.- 힙(heap)에 만료된 타이머가 있는 경우
- 이벤트 루프는 연결된 콜백을 가져와서,
- 타이머 대기열이 비어 있을 때까지, 지연의 오름차순으로 실행을 시작한다.
min heap은 데이터를 이진트리 형태로 관리하며 최솟값을 찾아내는데
- 효율적인 구조. 그래서 가장 먼저 실행되는 Timer를 손쉽게 발견할 수 있다.
만약
setTimeout(A function, 1000)를 하면 min heap에서 찾아서 실행하기에, 1초가 흐르기 전에 실행되는 것을 방지하고, 딱 1초 후에 실행되는 것은 아니다. 결국 1초 이후에 실행이 된다. 그리고 이것(타이머 콜백의 실행)은 poll phase가 제어(control) 합니다.
💡 Poll 심화
Event Loop가 Poll Phase에 들어왔다면, 다음과 같은 Queue에 들어 있는 일을 처리한다.
- 데이터베이스 관련 작업으로 인한 결과가 왔을 때 실행되는 콜백
- HTTP 요청으로 인한 응답이 왔을 때 실행되는 콜백
- 파일을 비동기로 읽은 후에 실행되는 콜백
이 과정 또한 Queue가 비거나 시스템의 실행 한도 초과에 다다를 때까지 계속된다. Poll Phase는 또한 다른 Phase와 다르게 Poll Phase에 일이 다 소진되더라도, 바로 다음 Phase로 이동하는 것은 아니다. 이벤트 루프가 Poll 단계에 들어가고 예약된 타이머가 없으면, 다음 두 가지 중 하나가 발생한다.
폴 큐가 비어 있지 않은 경우 이벤트 루프는 큐가 소진되거나 시스템의 실행 한도에 도달할 때까지 동기적으로 콜백을 실행하는 콜백 큐를 반복된다.
폴 큐가 비어 있으면 다음 두 가지 중 하나가 더 발생한다.
- 스크립트가 setImmediate()에 의해 예약된 경우 이벤트 루프는 폴링 단계를 종료하고 예약된 스크립트를 실행하기 위해 Check 단계로 계속된다.
- 스크립트가 setImmediate()에 의해 예약되지 않은 경우 이벤트 루프는 콜백이 Queue에 추가될 때까지 기다렸다가 즉시 실행한다.
폴 큐가 비어 있으면, 이벤트 루프는 시간 임계값에 도달한 타이머를 확인한다. 하나 이상의 타이머가 준비되면 이벤트 루프는 타이머 단계로 돌아가 해당 타이머의 콜백을 실행한다.
💡 Check 심화
이 단계에서는 Poll 단계가 완료된 직후 콜백을 실행할 수 있습니다. 폴 단계가 Idle 상태가 되고 스크립트가 setImmediate()를 사용하여 Queue에 지정된 경우, 이벤트 루프는 기다리지 않고 Check 단계를 계속할 수 있습니다.
12. setImmediate vs setTimeout vs process.NextTick
1setTimeout(() => console.log('timeout'), 0)2setImmediate(() => console.log('immediate'))3process.nextTick(() => console.log('nextTick'))4console.log('current event loop')56/** 결과7* current event loop8* nextTick9* timeout10* immediate11*/
처리되는 단계
setTimeout(),setInterval()는Timers 단계에서 처리setImmediate()는Check 단계에서 처리process.nextTick()은 이벤트 루프 시작 시와 이벤트 루프의각 단계 사이에서 처리.
12.1 process.nextTick() 재귀 호출 시 이벤트 루프가 blocking됨
주어진 단계에서 process.nextTick()이 호출되면 이벤트 루프가 계속되기 전에 process.nextTick()에 전달된 모든 콜백이 해결된다.
이렇게 process.nextTick()이 재귀적으로 호출하면, 이벤트 루프를 차단하게 된다.
반면에, setImmediate()가 재귀적으로 호출되더라도,
이벤트 루프를 차단하지 않으며 지정된 시간 초과 후에 setTimeout() 콜백이 실행된다.
모든 경우에 setImmediate()를 사용하기를 추천하는데,
사용하기 쉽고 browser 등의 다양한 환경에서 호환이 더 잘된다.
13. Node.js Event Emitter
브라우저에서 JavaScript로 작업한 경우 마우스 클릭, 키보드 버튼 누르기, 마우스 움직임에 대한 반응 등 과 같은 이벤트를 통해 사용자 상호 작용이 얼마나 처리되는지 알 수 있습니다.
이러한 것 처럼 백엔드 측에서 Node.js도 event-driven 시스템을 이용해서 작동 됩니다.
13.1 Observer Design Pattern
event-driven 시스템을 이용하는 것을 Observer Design Pattern이라고도 부른다.
1Subject2---------------------------3| | | |4Observer Observer Observer Observer
- 이 패턴에는 특정 Subject를 관찰하는 많은 Observer가 있다.
- 관찰자(Observer)는 기본적으로 Subject에 관심이 있고, 해당 주제 내부에 변경 사항이 있을 때 알림을 받는다.
- 그래서 그들은 그 주제에 스스로를 등록(Register)한다.
- 주제에 대한 관심을 잃으면, 단순히 해당 주제에서 등록을 취소한다.
- 때때로 이 모델은
게시자-구독자(Publisher-Subscriber)모델이라고도 한다.
e.g. 트위터 팔로워가 많은 유명인을 생각할 수 있다.
- 이 팔로워들 각각은 자신이 좋아하는 유명인의 최신 업데이트를 모두 받고 싶어한다.
- 따라서 관심이 지속되는 한 유명인을 팔로우할 수 있다.
- 그가 흥미를 잃으면 그는 단순히 그 유명인을 따르는 것을 중단한다.
- 여기서 우리는 추종자를
관찰자(Observer)로, 유명인을주체(Subject)로 생각할 수 있다.
13.2 Event Emitter 클래스
- Node.js도 Event 모듈을 사용하여 유사한 시스템을 구축할 수 있는 옵션을 제공합니다.
- 특히 이 모듈은 이벤트를 처리하는 데 사용할 EventEmitter 클래스를 제공합니다.
- cf. https://nodejs.dev/en/learn/the-nodejs-event-emitter/