본문 바로가기

STUDY/# Node.js

Back-End API 서버 구현을 위한 Node.JS 선택의 이점과 단점 # 2

반응형

by commin

Back-End API 서버 구현을 위한 Node.JS 선택의 이점과 단점

요 약

해당 문서에서는 Node.js의 만들어진 배경에 대해서 먼저 조사하고, Node.js를 알기 위한 기본 구조 및 주요 개념들에 대해 서술합니다. 또한 장점과 단점을 정리하고 Spring Framework와 비교를 통해 앞으로 Back-End 프레임워크 선택 시 도움이 되고자 합니다.

목차
#1 --------------------------- 1  Introduction
#2 --------------------------- 2  Node.js Main Concept
#2 --------------------------- 2.1   Single Thread로 동작하는데 어떻게 동시성을 가질 수 있을까? (이벤트 기반 비동기 방식)
#2 --------------------------- 2.2   Libuv.
#2 --------------------------- 2.3   Event Loop.
#2 --------------------------- 2.4   이벤트루프 실행단계
#2 --------------------------- 2.5   Nonblocking I/O
#3 --------------------------- 3  Advantages and Disadvantages Node.js
#3 --------------------------- 3.1   Node.js 의 장점
#3 --------------------------- 3.2   Node.js 의 단점
#3 --------------------------- 4  Spring과  Node.js 비교
#3 --------------------------- 4.1   Spring vs Node.js
#3 --------------------------- 5  Conclusion.

2      Node.js Main Concept

2.1       Single Thread로 동작하는데 어떻게 동시성을 가질 수 있을까? (이벤트 기반 비동기 방식)

Node.js Single Thread 기반의 서버사이드 기술이라고 알려져 있어서 내부적으로도 쓰레드가 하나라고 생각 할 수 있지만, 그것은 사실이 아닙니다.

Node.js는 기본적으로는 libuv 라이브러리를 사용하는데 이 안에서 event loop라는 것을 사용합니다. Event loop는 하나의 Thread를 사용하고, 이곳에서 개발자가 작성한 Callback Function이 실행됩니다. 그리고 내부적으로 쓰레드 풀(Thread Pool)이 존재합니다.

자바스크립트 엔진 자체는

콜스택에 쌓인 실행 컨텍스트에 따라 위에서부터 순차적으로 실행이 이루어지기 때문에 비동기 처리를 할 수 없습니다. 때문에 비동기 처리가 필요한 경우에는 Node API를 통해 libuv 라이브러리에 이벤트를 넘기고 자기는 다른 일을 하다가 작업이 완료되면 다시 받아와서 처리하는 식으로 비동기 처리를 하는 것입니다. 이것이 바로 node.js의 핵심 컨셉트 중 하나인 [이벤트 기반의 비동기 방식]입니다.

Node.js는 그래서 싱글스레드 논블로킹 모델이라고 부르는데, 하나의 쓰레드로 동작하지만 비동기 I/O 작업을 통해서 요청들을 서로 블로킹 하지 않는다는 뜻이죠. 즉 동시에 많은 요청들이 들어와도 비동기로 수행함으로써 싱글 쓰레드임에도 불구하고 논 블로킹 서비스가 가능한 것 입니다.

2.2       Libuv

Node.js는 기본적으로 libuv 위에서 동작하고 있고 node 인스턴스가 메모리에 할당되어 실행될 때, libuv에 워커 스레드풀(default 4)이 생성되어 관리됩니다.

Node.js에 블로킹 작업(api, DB I/O, File I/O )이 들어오면 이벤트루프가 uv_io에게 작업을 전달합니다. Libuv는 커널단에서 지원해주는 비동기작업이 정의되어 알고 있기 때문에 이런 작업들은 커널에서 지원해주는 비동기 함수들을 호출하고 작업이 완료되면 시스템콜[1]을 다시 전달받아 libuv 안에 있는 이벤트루프에게 콜백으로 등록하여 순차적으로 처리합니다.

커널이 지원하지 않는 비동기작업들은 libuv가 처음 생성한 스레드풀에서 처리합니다. 대표적으로는 File I/O 작업이 있고 이런종류의 작업은 libuv의 워커 스레드풀에서 수행하고 작업이 완료되면 다시 이벤트루프에 콜백으로 등록하여 순차적으로 처리합니다.

2.3       Event Loop

이벤트 루프에 대해서 좀 더 자세히 알아보자면, 이벤트 루프는 node.js에서 메인 스레드 겸 싱글 스레드로 동작하고 비즈니스 로직을 수행하는 역할을 합니다.

보통 웹 어플리케이션에서 예로 들어보자면 request가 들어왔을 때, 경로 라우터태우기, If문 분기, 반복문, Database에서 데이터를 읽어오거나 외부 API 콜을 하는 것은 libuv의 워커 쓰레드가 수행합니다.

동시에 여러 요청이 들어오더라도 1개의 이벤트루프에서 처리합니다. 따라서 JS로직이 무겁다면 많은 요청을 처리하기 힘듭니다. 예시로 한 개의 요청에서 for문이 for(var i=0; i<10000000000; i++) 이렇게 돌면 그 후에 들어온 요청은 for문이 끝나기 전까지 pending상태가 됩니다.

게다가 GC(가비지 컬렉션)까지 이벤트 루프에서 돌고 있어서 이벤트루프에서 어떤 특정 코드가 메모리를 선점하고 끝나지 않으면 메모리가 부족해서 뻗게 됩니다.

2.4       이벤트루프 실행단계

1.     Timers

setTimeout(), setInterval() 과 같은 타이머 콜백들을 처리합니다.

사용자가 구현한 콜백함수는 지정된 시간 후에 poll 큐에 넣고 poll 큐가 실행될 때 처리되게끔 하는데 때문에 지정된 시간 후에 poll 큐가 비어 있지 않은 상태면 poll 큐에 있는 작업이 모두 끝난 후에 실행되므로 지정된 시간이후에 바로 실행되는 것을 보장하지 않습니다.

2.     I/O Callbacks

Timer 콜백, Close 콜백 등을 제외한 대부분의 콜백들을 관리합니다. (Http, apiCall, DB I/O )

사용자가 구현한 콜백 내부 로직은 역시 poll 큐에 등록하고 poll 큐가 실행될 때 순차적으로 실행됩니다.

3.     Poll

poll큐에 등록된 콜백 로직을 처리합니다.

4.     Check

setImmediate() 콜백은 이곳에서 호출되고 실행됩니다.

5.     Close

.on(‘close’,…) 같은 형태로 구현된 콜백은 이곳에서 실행됩니다.

2.5       Nonblocking I/O

일단 공통적으로 I/O는 서비스 프로세스 (User-Space Application)에서 수행될 수 없고 커널 단(OS)에서 수행합니다.

I/O를 수행하기 위해서는 커널에게 시스템콜을 한번이라도 보내야 하는데, 시스템콜을 보내는 순간 커널에 제어권을 넘기게 됩니다. 제어권이 넘어가면 프로세스는 시스템으로부터 결과를 받아서 제어권을 받기 전까지는 프로세스는 block 상태가 됩니다.

I/O 작업을 하는 동안 프로세스가 block상태가 되는 방식을 blocking I/O 방식이라고 말합니다. 이와는 다르게 non-blocking I/O 방식은 프로세스가 I/O작업을 요청하는 순간 바로 응답을 해줌으로써 프로세스가 다른 작업을 수행할 수 있는 상태로 되돌려 놓고, 작업이 완료되었는지 중간중간 확인하는 방식으로 구현됩니다 (Sync Non-Blocking I/O)

node.js가 하나의 쓰레드에서 여러 요청을 동시에 수행하는게 가능한 이유는 이벤트루프도 중요하지만 위와 같은 Non-Blocking I/O 방식을 사용하기 때문입니다.

Node.js가 사용하는 Non-Blocking I/O는 위와는 약간 다른 Async Non-Blocking I/O이며 프로세스가 다른 작업을 수행하는 동안 OSlibuv의 워커스레드에서 백그라운드로 I/O작업을 처리하고 완료되면 이벤트루프에 알려주는 형식을 채택하고 있습니다. (Async Non-Blocking I/O)



[1] 프로그램이 I/O작업을 수행하려면 운영체제의 커널에 허락을 구해야 하며, 시스템 콜이란 프로그램이 I/O작업을 하기위해 커널에게 허락을 구하는 작업을 뜻합니다. 프로그램이 커널에 특정 요청을 담은 함수를 호출하는 것으로 구현됩니다.

-    [Node.js vs Spring boot :: 아는개발자] (https://syundev.tistory.com/229)

-    [Java Spring 과 Node.js 비교](https://selfish-developer.com/entry/Nodejs-vs-Spring-Boot)

-    [블로킹 vs 논블로킹](https://ju3un.github.io/network-basic-1/)

-    [NodeJS는 Single-Thread가 아니다](https://medium.com/@rpf5573/nodejs-nodejs%EB%8A%94-single-thread%EA%B0%80-%EC%95%84%EB%8B%88%EB%8B%A4-f02b0278c390)

-    [Blocking vs Nonblocking](https://medium.com/@rpf5573/nodejs-blocking-vs-non-blocking-8f92f2b522a7)

-    [Node.js는 싱글 스레드?](https://velog.io/@daeseongkim/Node.js-Node.js%EB%8A%94-%EC%8B%B1%EA%B8%80-%EC%8A%A4%EB%A0%88%EB%93%9C)

참고문헌

-    위키백과

-      Node.js를 활용한 웹 서버 성능 분석 (광운대학교 정보콘텐츠대학원 이형근)

-      모바일 웹 어플리케이션을 구현하기 위한 Node.js 파일에 대한 조사 (동의대학교 장종욱)

반응형