1. NodeJS 핵심 개념 이해

📌 노드가 무엇인지에 대해 여러 가지 의견이 많지만, 공식 사이트에서 나와 있는 노드의 설명을 말하자면
” Node.js 는 Chrome V8 Javascript 엔진으로 빌드된 자바스크립트 런타임 입니다. ”
라고 나와 있다.

공식 사이트에서 서버라는 얘기가 나와있지 않는 이유는 Node는 Sever만 실행 할 수 있는게 아니라 런타임과 같은 개발과 여러 라이브러리 모듈등 강력한 기능들을 통틀어 말하기 때문이다.

개념 리스트 목록

  1. 노드와 런타임
  2. 이벤트 기반
  3. Call Stack
  4. 논블로킹(Non-Blocking) I/O
  5. 싱글 스레드 모델

1. 노드란? 그리고 런타임?

Node는 자바스크립트 애플리케이션을 실행할 수 있지만, Node는 서버 애플리케이션을 실행하는 데 제일 많이 사용한다.

  • 자바스크립트 런타임
    • Node.js는 Chrome V8 Javascript 엔진으로 빌드된 자바스크립트 런타임이다.런타임은 특정 언어로 만든 프로그램들을 실행할 수 있는 환경을 뜻한다.Node는 쉽게 생각하면 자바스크립트 실행기라고 봐도 무방하다.
노드 기본 01

Node는 V8과 더불어 libuv라는 라이브러리를 사용한다. V8과 libuv는 C와 C++로 구현되어 있다 개발자가 구현한 코드는 Node가 알아서 V8과 libuv에 연결해주므로, C와 C++은 몰라도 무방하다.

💡 libuv 라이브러리는 노드의 특성인 이벤트 기반, 논블로킹 I/O 모델구현하고 있다.

Node 외의 런타임


Node가 가장 많이 사용하는 런타임이라 그렇지 많은 런타임이 있다. 여기서 눈여겨볼 런타임으로 bun, deno 런타임이 있다.

2. 이벤트 기반


💡 이벤트 기반(Event-driven) 이란? 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식을 의미 이벤트로는 클릭이나 네트워크 요청 등이 있을 수 있다.

  • 이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할지 미리 등록해둬야 한다.
  • 이를 이벤트 리스너(event listener)에 콜백(callback) 함수를 등록한다 표현한다.
    • 예시 : 버튼을 클릭할 때 경고창을 예를 든다면 경고를 띄우는 콜백 함수를 등록해주면 클릭 이벤트가 발생할 때마다 콜백 함수가 실행되고 경고창이 뜬다.
  • 노드도 이벤트 기반으로 동작하기 때문에 이벤트가 발생하면 이벤트 리스너에 등록된 콜백 함수를 호출한다.
Node-02
  • 발생한 이벤트가 모두 완료 되면, 노드는 다음 이벤트가 발생하기 전까지 대기한다.
  • 이벤트 기반 모델에서 등장하는 event loop라는 개념
    • 여러 이벤트가 동시에 발생했을 때 어떤 순서로 콜백 함수를 호출할지를 이벤트 루프가 판단한다.
    • 노드와 자바스크립트에서 이벤트 루프는 정말 중요한 개념이다.

호출 스택 영역

/*
 노드는 자바스크립트 코드의 맨 위부터 한 줄씩 실행한다.
 함수 호출 부분을 발견했다면 호출한 함수를 호출 스택(call stack)에 넣는다.
 말 그대로 Stack이다. 먼저 들어온게 나중에 나간다, 나중에 들어온게 먼저 나간다 (선입후출 or 후입선출)
*/

function first(){
	second()
	console.log('첫 번째')
}
function second(){
	third()
	console.log('두 번째')
}
function third(){
	second()
	console.log('세 번째')
}
first()

/* 
first() 함수가 제일 먼저 호출되고, 그 안의 second 함수가 호출된 뒤, 마지막으로 third 함수가 호출된다.
*/
Node-03
  • anonymous 함수는 처음 실행 시 전역 콘텍스트(global context)를 의미한다.
  • 콘텍스트란?
    • 함수가 호출되었을 때 생성되는 환경을 의미
  • 자바스크립트는 코드가 실행 시 기본적으로 전역 콘텍스트 안에서 돌아간다고 생각하면 좋다.
  • 함수는 실행되는 동안 호출 스택에 머물러 있다가 실행이 완료되면 호출 스택에서 지워진다.
    • third, second, first, anonymous 순으로 지워지고, anonymous 콘텍스트까지 실행이 모두 완료되면 호출스택은 비게 된다.

3. Call Stack

  • call stack을 파악하기 위해서는 Event Loop, Task Queue, Background를 알아야 한다.

💯 Call stack 파악을 위한 추상화 된 설명


  • 이벤트 루프
    • 이벤트 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할을 담당한다.
    • 노드가 종료될 때까지 이벤트 처리를 위한 작업을 반복하므로 루프(loop)라고 부른다.
  • 백그라운드
    • setTimeout 같은 타이머나 이벤트 리스너들이 대기하는 곳
    • 자바스크립트가 아닌 다른 언어로 작성된 프로그램이라고 봐도 무방하며, 여러 작업이 동시에 실행될 수 있다.
  • 태스크 큐
    • 이벤트 발생 후, 백그라운드에서는 태스크 큐로 타이머나 이벤트 리스너의 콜백 함수를 보낸다.
    • 정해진 순서대로 콜백들이 줄을 서 있으므로 콜백 큐라고 부르기도 한다.
    • 콜백들은 보통 완료된 순서대로 줄을 서 있지만, 특정한 경우 순서가 바뀌기도 한다.-
Node-call-01

실제로 태스트 큐가 하나의 큐처럼 보이지만 실제로는 여러 개의 큐로 이루어져 있다.
이벤트 루프는 정해진 규칙에 따라 콜백함수들을 호출 스택으로 부른다.

이벤트 루프

  • 전역 콘텍스트인 anonymous가 호출 스택에 들어간다.
  • 그 뒤 setTimeout이 호출 스택에 들어간다.
  • 호출 스택은 스택을 따르기 때문에 setTimeout을 먼저 실행한다.
  • setTimeout이 실행되면 타이머와 함께 run 콜백을 백그라운드로 보내고, setTimeout은 호출 스택에서 빠진다.
  • 그 다음 컨택스트인 anonymous가 호출 스택에서 빠진다.
  • 백그라운드에서 3초를 센 후 run 함수를 태스크 큐로 보낸다.
    • 3초를 세었다는 것은 백그라운드에 맡겨진 작업이 완료되었다는 것으로 이해해도 된다.
Node-call-02
Node-call-03

호출 스택에서 anonymous까지 실행이 완료되어 호출 스택이 비어 있는 상황

  • 이벤트 루프는 호출 스택이 비어 있으면 태스크 큐에서 하나씩 함수를 가져와 호출 스택에 넣고 실행한다.
  • 이벤트 루프가 run 콜백을 태스크 큐에서 꺼내 호출 스택으로 올린 상황
  • 호출 스택으로 올려진 run은 실행되고, 실행 완료 후 호출 스택에서 제거 된다.
  • 이벤트 루프는 태스크 큐에서 콜백함수가 들어올 때까지 게속 대기한다.

왜 setTimeout의 시간이 정확하지 않을까?


💡 호출 스택에 함수가 너무 많이 들어 있으면 3초가 지난 후에도 run 함수가 실행되지 않을 수 있다. 이벤트 루프는 호출 스택이 비어 있을 때만 태스크 큐에 있는 run 함수를 호출 스택으로 가져오기 때문이다. 이것이 setTimeout의 시간이 정확하지 않을 수도 있는 이유다.

4. 논블로킹(Non-Blocking) I/O

💡 이벤트 루프를 잘 활용하면 오래 걸리는 작업을 효율적으로 처리할 수 있다. 작업에는 두 가지 종류가 있다.

  • 동시에 실행될 수 있는 작업
  • 동시에 실행될 수 없는 작업
  • 기본적으로 자바스크립트 코드는 동시에 실행될 수 없다.
  • 자바스크립트 상에서 돌아가는 것이 아닌 I/O 작업 같은 것은 동시에 처리될 수 있다.
  • I/O란 입력(input), 출력(output)을 의미한다. (파일 읽기 및 쓰기, 폴더 만들기 등)이나 네트워크를 통한 요청 같은 작업이 I/O의 일종
  • 이러한 작업을 할 때 노드는 논블로킹 방싱으로 처리하는 방법을 제공한다.

Non-blocking이란? 이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행하는 것을 의미한다. 반대인 “blocking”은 이전 작업이 끝나야만 다음 작업을 수행하는 것을 의미한다.

옆에 그림을 보면 블로킹 방식보다 논블로킹 방식이 같은 작업을 더 짧은 시간에 처리한다는 것을 알 수 있다.다만, 작업들이 모두 동시에 처리될 수 있는 작업이라는 전제가 있다.

  • 노드는 I/O 작업을 백그라운드로 넘겨 동시에 처리하곤 한다.
  • 동시에 처리될 수 있는 작업들은 최대한 묶어서 백그라운드로 넘겨야 시간을 절약할 수 있다.

그림 1-10을 보면 순서의 중요성을 알 수 있다. 처리하는데 각각 1초가 걸리는 작업 다섯 개가 있는데 그중 세개는 동시에 처리가 가능하고 두 개는 동시에 처리할 수 없다.

동시에 처리될  있는 I/O 작업이더라도 논블로킹 방식으로 코딩하지 않으면 의미가 퇴색되므로
논블로킹 방식으로 코딩하는 습관을 들여야 한다.

// 블록킹 방식
function longRunningTask(){
	// 오래 걸리는 작업
	console.log('작업 끝')
}
console.log('시작')
longRunningTask()
console.log('다음 작업')

// 논블로킹 방식
function longRunningTask(){
	// 오래 걸리는 작업
	console.log('작업 끝')
}
console.log('시작')
longRunningTask()
setTimeout(longRunningTask, 0)
console.log('다음 작업')

5. 싱글 스레드

이벤트 기반, 논블로킹 모델과 더불어 노드를 설명할 때 자주 나오는 용어가 있다.
싱글 스레드다. 싱글 스레드란 스레드가 하나뿐이라는 것을 의미한다. 지금까지 작성한 코드가 동시에 실행될 수 없는 이유이기도 하다.

Node-04
  • 노드는 엄밀히 말하면 싱글 스레드로 동작하지는 않는다.
  • 노드를 실행하면 프로세스가 하나 생성된다.
  • 그 프로세스에서 스레드들을 생성하는데, 이때 내부적으로 스레드를 여러 개 생성한다.
  • 그중에서 직접 제어할 수 있는 스레드는 하나뿐이다. 그래서 흔히 노드가 싱글 스레드라고 여겨지는 것이다.
  • 스레드를 이해하기 위해서는 프로세스부터 알아야 한다.
    • 프로세스는 운영체제에서 할당하는 작업의 단위다. ( 프로세스 간에 메모리등의 자원을 공유하지 않음)
    • 스레드는 프로세스 내에서 실행되는 흐름의 단위이다. 프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있다. 스레드들은 부모 프로세스의 자원을 공유한다. 같은 주소의 메모리에 접근 가능하므로 데이터를 공유할 수 있다.

하나의 스레드만 직접 조작할 수 있으므로 일손이 하나다.
요청이 많이 들어오면 한 번에 하나씩 요청을 처리한다.
블로킹이 심하게 일어나는 작업을 처리하지 않는다면 스레드 하나로도 충분하다. 블로킹이 발생할 것 같은 경우에는 논블로킹 방법으로 대기 시간을 최대한 줄인다.

– 중요 –

싱글 스레드와 블록킹 그리고 논블로킹 사용 설명 및 비교

LIST