Node.js

Node.js는 간단하게 이야기 한다면, 비동기 이벤트 기반 Javascript 런타임이다. 자바스크립트를 실행시켜 어떠한 프로그램을 작성할 수 있는데, 서버를 작성하는 것이 일단은 주 목적이다. 따라서 서버사이드 자바스크립트라는 대명사로 불리기도 한다. Node.js로 웹 서버를 프로그래밍 하게 되면 프론트앤드에서 사용하는 스크립트 역시 자바스크립트이기 때문에, 백엔드와 프론트앤드 둘 다 동일한 언어로 작성을 하게 되어서 언어 학습 오버헤드가 줄어들 것을 기대할 수 있고, 언어 자체가 매우 유연하기 때문에 간단한 서버는 빠르게 작성할 수 있다.

주요 특징들

노드의 의도된 용도(Intended Purpose)

노드의 주요 특징들이 있다. 공식 사이트에 설명된 대로, 이벤트 드리븐(Event-Driven), 논 블로킹 I/O(Non-Blocking I/O), 그리고 npm을 통한 패키지 에코시스템의 제공 등이 있다.

노드는 Scalable한 네트워크 어플리케이션을 제작하기 위해 만들어졌다. Scalable하다는 것은 크기를 늘였다 줄였다 자유롭게 할 수 있다라는 의미로 보면 된다. Lightweight한 규모부터 매우 Heavy traffic을 감당할 수 있는 규모까지 아우른다고 보면 된다.


이벤트 드리븐 구조

다수의 연결 요청은 concurrent하게 처리된다. Concurrent라는 것은 한번에 여러개가 동시에 처리된다는 뜻으로 보면 된다. Parallel과 다른 점은, Parallel은 실제 물리적으로 동시에 처리가 되는 것이고, Concurrent는 물리적으로는 동시가 아니지만, 겉보기에는 동시에 처리되는 것 처럼 보인다, 즉 논리적으로 동시에 처리가 된다는 차이점이 있다.

매 연결이 이루어질 때 마다 callback이 실행되지만 만약 처리할 일이 없으면 Node는 sleep상태로 쉬고 있게 된다.


Non-Blocking I/O

이러한 방식은 최근에 가장 많이 쓰는 OS 쓰레드를 사용한 concurrency 모델과는 다르다. 쓰레드 기반 네트워크는 비교적 비효율적이고 사용하기 매우 어렵다. 게다가 노드에서는 프로세스의 데드락과 같은 상황을 고려하지 않아도 된다. 왜냐면, lock을 사용하지 않기 때문에! Node에 있는 대부분의 함수는 I/O를 직접적으로 수행하지 않기 때문에, 노드 프로세스는 절때 block되지 않는다. Block이 없기 때문에 노드로 확장성 있는 시스템을 작성하면 좋다.


이벤트 루프 구조

노드의 설계는 루비의 이벤트 머신과 파이썬의 Twisted의 영향을 받았고, 따라서 비슷한 구조를 갖는다. 노드는 거기에 이벤트 모델이 적용되었고, 런타임을 만드는데에 라이브러리로서가 아닌 이벤트 루프가 존재하게 되었다. 다른 시스템의 경우 이벤트 루프를 시작하기 위해 항상 blocking 호출이 선행된다. 일반적으로 어떠한 동작을 할 것인지의 내용은 스크립트 시작 부분의 콜백을 통해서 시작되며, 그리고 스크립트 끝 부분에는 EventMachine::run()과 같은 blocking 호출을 통해서 서버가 시작됩니다. 노드에는 start-the-event-loop 와 같은 함수 호출이 없습니다. 노드는 단순히 서버 스크립트를 실행하고 나서 이벤트 루프로 들어갑니다. 더이상 처리해야할 콜백이 없을 경우 노드는 이벤트 루프를 탈출한다. 이러한 동작 방식은 브라우저에서 자바스크립트가 실행되는 방식과 비슷하다. 이벤트 루프가 유저로 부터 숨겨져 있는 방식이다.


HTTP는 스트리밍과 짧은 latency을 염두해 두고 설계된 Node의 첫번째 모듈이다. 따라서 Node는 웹 라이브러리 또는 프레임 워크의 기반으로 적합하다.


노드는 쓰레딩을 사용하지 않도록 설계되었다고 해서 당신의 시스템의 멀티코어를 활용하지 못하는 것은 아니다. child_process.fork() API를 이용하여 자식 프로세스를 만들어서 해당 프로세스와 쉽게 통신을 할 수 있다. 같은 인터페이스를 기반으로 구축된 cluster 모듈을 통해서 프로세스간 소캣을 공유하여 여러개의 코어에 로드 밸런싱을 할 수 있도록 해준다.


관련 개념들

Blocking vs Non-Blocking

Node.js에서 blocking 호출과 non-blocking 호출의 차이점에 대한 내용이다. 여기서는 이벤트 루프와 libuv에 대하여 언급하지만, 해당 주제에 대해 선행지식이 필요하지는 않다. 독자는 기본적인 자바스크립트 언어와 Node.js 콜백 패턴에 대한 지식이 있다고 가정한다.

I/O는 주로 libuv가 지원해주는 시스템의 디스크와 네트워크와의 상호작용하는 것을 뜻한다.

Blocking

Blocking은 Nodejs에서 추가적인 자바스크립트를 실행할 때, 프로세스가 non-자바스크립트 동작이 끝날 때 까지 기다려야 하는 것을 뜻한다. 이러한 현상이 왜 일어나냐 하면은, 이벤트 루프는 blocking 연산이 일어나고 있을 때에는 자바스크립트를 실행할 수 없기 때문이다. 노드에서는 I/O와 같은 자바스크립트가 아닌 동작을 기다리는 게 아니라, CPU intensive한 동작을 실행하기 위해서 자바스크립트의 성능이 저하된 경우에는 blocking이라고 부르지 않습니다. libuv를 사용하는 Nodejs 표준 라이브러리의 Synchronous 메소드는 가장 일반적인 blocking 작업입니다. 네이티브 모듈도 blocking 메소드를 가지고 있다.

Nodejs 표준 라이브러리에서 I/O 메소드는 모두 비동기 버전을 지원합니다. 요 녀석들은 non-blocking이고, 콜백 함수를 받습니다. 그리고 일부 메소드들은 blocking 버전도 있으며, 그녀석들은 이름이 Sync로 끝난다.

Blocking 함수들은 synchronous하게, 그리고 non-blocking 함수들은 asynchronous하게 실행된다.

Concurrency와 Throughput

Nodejs에서 자바스크립트 실행은 싱글 쓰레드이다. 따라서 동시성(Concurrent)은 이벤트 루프가 다른 작업을 끝내고 자바스크립트 콜백 함수들을 실행시킬 수 있는 능력을 뜻한다. 동시성 방식으로 실행되게 하고 싶은 코드는 I/O와 같이 자바스크립트가 아닌 작업을 실행하게 해서 이벤트 루프가 다른 작업을 실행시키게 해 주어야 한다.

예를 들어서, 웹 서버로 온 요청을 처리하는데에 50ms의 시간이 걸리고, 45ms는 비동기로 처리 가능한 데이터베이스 I/O라고 하자. Non-blocking 비동기 작업을 하게 되면, 요청 마다 있는 45ms의 시간은 다른 요청을 처리하는데 사용될 수 있다. 이는 Blocking 메소드를 선택했을 때에 비해 Non-Blocking 메소드를 선택했을 때 갖는 서버 capacity의 엄청난 차이이다.

이벤트 루프는, 동시성을 위해서 추가적인 쓰레드를 만들 수 있는 다른 언어의 동시성 모델과는 다르다.

Blocking코드와 Non-Blocking 코드를 섞어 쓸 시 위험성

I/O를 처리할 때 안티패턴들이 좀 있다. 다음 예제를 보자
 

const fs = require('fs');
fs.readFile('/file.md', (err, data) => {
  if (err) throw err;
  console.log(data);
});
fs.unlinkSync('/file.md');


위 예제를 보면 fs.unlinkSync()는 fs.readFile()이 실행되기 전에 실행 될 것같다. 실제 file.md가 읽어지기 전에 말이다.

따라서 순서가 제대로 보장되게 하기 위해서 non-blocking으로만 작성해서 맞는 방법으로 코드를 다시 작성해 보았다.

 

const fs = require('fs');
fs.readFile('/file.md', (readFileErr, data) => {
  if (readFileErr) throw readFileErr;
  console.log(data);
  fs.unlink('/file.md', (unlinkErr) => {
    if (unlinkErr) throw unlinkErr;
  });
});


fs.unlink() 함수는 fs.readFile()함수의 콜백 내부에서 호출되어서, 맞는 호출 순서를 보장하게 된다.

참고

https://nodejs.org/en/about/
https://nodejs.org/en/docs/guides/blocking-vs-non-blocking/


+ Recent posts