Search
▪️

Starting Node.js

Node Basic

Node는 JavaScript RunTime, 즉, RunTime이다.
RunTime이란 무엇인가? → 컴퓨터 내의 JavaScript가 실행되는 동안의 모든 동작들을 말한다. 이중에서 Node.js는 RunTime Environment의 정의와 조금 더 유사하다. RunTime Environment라고 함은 컴퓨터에서 실행되는 프로세스나 프로그램을 위한 소프트웨어 서비스를 제공하는 가상 머신이다.
Node.js에는 실제로 VM이 들어있다. 즉, JavaScript는 원래 Web Browser상에서 동작하는 언어지만, Node.js를 통해서 Web Browser 바깥에서도 동작할 수 있게 되었다.
기본 Terminal에서 node로 Node.js를 실행하면 READ, EVALUATE, PRINT, LOOP가 가능한 REPL 상태가 된다.

Call Stack & Event Loop

비동기적 Code 덕에 단순히 Call Stack 만으로는 실행 결과에 대해서 설명이 불가능하다. 만일 동기적 Code라고 한다면, Call Stack에 들어간 순서대로 실행 결과가 나올텐데 말이다. 따라서 여기서 필요한 개념이 Event Loop라고 볼 수 있다.
Event Loop 개념 도입에 따라서 단순히 Call Stack만 이용하는 것이 아니라 Task Queue라는 자료구조도 쓰이게 된다.
A라는 Function은 Function의 Call에 따라서 Stack에 들어갔다가 나오는 과정이라고 하자. A Function을 Call시 나중에 수행되어야 하는 작업이 있다면 이 작업을 Task Queue에 넣게 되고 A Function은 Call Stack에서 빠지게 된다. Task Queue에 넣어 놓았던 작업이 실행될 때, 해당 작업을 Call Stack에 넣고 작업 완료 시에 Call Stack에서 제거한다.
위와 같이 Call Stack과 Task Queue가 사용된다고 할 때, Event Loop는 어떤 역할을 담당하는가? → Call Stack이 비어 있는 상태일 때 (실행될 Code가 남았어도 어쨌든 Call Stack이 비어 있을 때), Task Queue의 작업을 Call Stack에 불러들이는 역할을 한다. 단, Event Loop가 Task Queue의 작업들을 불러오기 위해선 작업이 실행될 수 있는지 Condition들을 확인한 후에 불러온다.
그렇다면 언제 Task Queue에 작업들이 들어가게 되는 것인가? → setTimeout, setInterval, setImmediate, Promise(resolve, reject), Async-Await, Event Listener의 Callback Function들과 같은 작업들에 대해서 Task Queue에 넣게 된다.
일반적으로 이런 Task Queue는 한 개만 두는 것이 아니라 우선 순위에 따라서 여러 개가 존재한다. 이렇게 여러 Queue가 존재하고, 여러 작업들은 각 Task Queue에 분류 되어 나눠서 들어가게 되는데도, Event Loop는 각 Task Queue에 존재하는 작업들을 순서대로 Call Stack으로 불러오는 것이 가능하다. (즉, Event Loop는 정해진 순서대로 작업들을 불러오는 것인데 이 순서들은 이미 Event Loop가 알고 있다.)

Event Driven, Single Thread, Non-Blocking IO

Event Driven
Node.js를 통해서 Server를 구현하고자 한다면, Call Stack, Task Queue, Event Loop외에도 한 가지 더, Node.js가 계속 구동될 수 있도록 하는 장치가 필요하다.
Server라는 것은 Client로부터 Request를 받으면, Client에게 Response를 보내주는 것이라고 볼 수 있다. 이 때, Server는 Request가 언제 올지 모르기 때문에 Event Listener를 달아둔 상태로 Request를 받기 위해서 대기한다. Request가 들어오면 Event Listener가 서버에게 Request가 들어왔다고 알리는 방식이다.
이런 Event Listener를 Event Loop와 묶어서 생각해보자. 일반적으로는 Event Listener에 등록된 Callback Function들이 있기 때문에, Event가 발생하면 해당 Callback들이 Task Queue에 들어가게 되고, Condition들을 만족하게 되면 우선 순위에 맞춰서 Event Loop가 Call Stack으로 불러서 해당 작업들을 실행하게 되는 것이다.
쉽게 생각하면 Server가 구동 될 때, Call Stack과 Task Queue가 비어 있을 수 있는데 Server가 여전히 구동 가능한 이유는 Event Listener에 등록된 Callback Function을 가지고 있어서 가능한 것이다. 이것이 Event Driven에 따른 Server Running이다.
Non-Blokcing IO
Non-Blocking의 개념 역시 Event Loop, Call Stack, Task Queue로 설명이 가능하다.
Call Stack이 Task Queue에 보내는 동작이 가능하다면 Non-Blocking이라고 보면 된다.
IO 라고 하는 것은 File System의 IO, Network IO 크게 두 가지로 나눠서 볼 수 있다. 이 두 가지 방법의 IO는 Non-Blocking으로 동작한다. 참고로 File System은 Non-Blocking일 뿐 아니라, 작업 시 Multi Thread로 작업을 할 수 있다. 따라서 Node가 Single Thread로 동작함에도 Multi Thread 작업을 할 수 있는 것이다.
Node의 경우 작업 자체를 Multi Thread를 이용할 수 없는 것이 아니다. Call Stack에서 Task Queue로 보내고 Event Loop로 작업을 불러오는 것과 같이 Event에 대한 처리를 Single Thread로 밖에 못하는 것이다. 따라서 Event에 대한 작업 처리를 한 번에 하나 밖에 못하기 때문에 Blocking이 발생하면 Node에서는 성능이 떨어지게 되는 것이다.
** Callback Function이라고 무조건 Task Queue에 들어가게 되는 것은 아니다. 어떤 Callback Function들은 바로 Call Stack에 들아가서 실행되는 경우도 있다. 바로 Task Queue에 들어가게 되는 Callback Function들은 Event Listener에 달려 있는 Callback Function들이다. Event 발생 즉시 Task Queue에 들어가게 된다.
** Single Thread... Node에서 Single Thread가 이용되고 이것이 더 좋다고 말하는 것은 어떤 것이 좋다고 하는 것인가? → Multi Thread를 이용하지 않음에 따라서 Coding의 난이도가 많이 낮아진다. (Thread 제어권 관련하여 말이다.) Node에서 이런 Single Thread 처리를 보완 하고자 Multi Thread가 아니라 Multi Processing을 하기도 한다.