Node.js 란?

728x90

Node.js는 서버사이드 자바스크립트이며 구글의 자바스크립트 엔진인 V8을 기반으로 구성된 (일종의) 소프트웨어 시스템이다.

이벤트 기반으로 개발이 가능하며 Non-Blocking I/O를 지원하기 때문에 비동기식 프로그래밍이 가능하다. 이 때문에 I/O 부하가 심한 대규모 서비스를 개발하기 적합하다고 할 수 있다. (또한 자바스크립트의 표준 라이브러리(CommonJS)의 스펙을 따르고 있다)

 

노드의 탄생 배경

노드는 2009년 라이언 달(Ryan Dahl)이 고안해 낸 언어로 같은 해 JSConf EU 컨퍼런스에서 처음 발표돼었다. 

노드가 개발된 배경과 목적은 다수의 연결을 효율적으로 관리하고 비용을 최소화할 수 있는 네트워크 소프트웨어를 개발하기 편리한 방법을 제공하기 위함이었다. 기존의 웹 애플리케이션 개발 방법은 최초에는 CGI같은 펄 등의 스크립트로 개발했었다. 이후에는 ASP, JSP, PHP 등의 웹 개발을 위한 전용 스크립트 언어들이 득세하였다. 특히 PHP의 경우 오픈 소스로서 그 개발의 용이함 때문에 많은 웹 애플리케이션과 웹 서비스들이 PHP로 구현되기도 했다. 노드는 이러한 시대 이후에 등장한 것으로써 자바스크립트서버에서 사용하고자 하는 노력이 반영된 결과이다. 

가장 널리 쓰이는 JSP나 PHP와 같은 언어로 웹 애플리케이션을 개발한다고 한다면 이 웹 애플리케이션은 일반적으로 아파치와 같은 웹 서버에서 동작하게 될 것이다. 이 때 어떤 클라이언트가 웹 서버에 연결을 요청하게 된다면, 일정한 메모리 공간을 사용하여 새로운 쓰레드를 생성한다. 이런 형태로 웹 애플리케이션을 개발하여 서비스를 제공하는 경우에는 더 많은 사용자를 지원하기 때문에 사업자는 더 많은 서버를 추가할 수 밖에 없었다. 이는 서버 구매비용뿐만 아니라 운영하는 비용이나 운영에 따라 발생하는 트래픽 비용, 인건비 등 여러 비용이 추가적으로 발생하는 문제가 생겼다. 게다가 여러대의 서버를 사용하더라도 사용자 입장에서는 마치 하나의 서버에 접속하는 것과 같은 효과를 주어야 하기 때문에 모든 서버들은 같은 데이터를 동기화해야 한다는 문제를 발생시키기도 한다. 

그래서 노드가 이런 문제를 해결하기 위해서 등장했다. 

노드는 서버에서 클라이언트로부터의 요청, 즉 연결을 처리하는 방법을 새로운 컨셉으로 변경하여 이 문제를 해결한다. 기존에는 각 연결에 대해 새로운 쓰레드를 생성하고 그에 따라 메모리를 할당하여 사용자 요청을 처리했다면, 노드에서는 각 연결이 하나의 이벤트로서 노드 엔진이 처리된다.

 

Node.js를 사용하는 서비스들


노드는 다른 언어들 만큼 등장한 지 오래되지 않았음에도, 이를 실제로 적용한 기업들의 사례가 늘어나고 있다. 

 

LinkedIn

유명한 SNS 서비스 중 하나인 LinkedIn은 자사의 모바일 웹 관련 서비스를 위해서 노드를 활용하고 있습니다. LinkedIn의 경우도 노드와 mongoDB를 활용하고 있습니다. 대표적인 사례로 자신들의 메인 서비스를 Ruby On Rails로 돌릴 때는 15대의 서버에서 15개의 인스턴스로 돌리다가, 노드로 개발한 뒤에는 두 배에 달하는 트래픽을 단지 4개의 인스턴스 만으로도 운영이 가능하다고 합니다. 또한 링크드인의 인턴쉽을 수행하던 사람들이 24시간동안의 핵데이HackDay를 통해 노드를 이용하여 VNC 클라이언트를 구현한 재미있는 케이스도 있습니다.

Cloud9IDE

Cloud9IDE는 가장 널리 쓰이고 있는 웹기반 통합개발환경으로서 웹브라우저에서 노드 기반의 애플리케이션을 쉽게 개발할 수 있도록 해주는 서비스입니다. 노드로 웹애플리케이션을 개발할 수 있는 환경을 제공하지만 그 서비스 자체도 노드를 이용하여 개발되어 있습니다. 사용자와 직접 상호 작용하는 프론트엔드 부분을 HTML5/CSS,/Javascript로 제작함과 동시에 서버에서 실질적으로 작업을 수행하는 백엔드에서도 노드를 사용하여 하나의 개발 언어만으로 전체 서비스를 개발하였습니다. 

 

이벤트 기반 비동기 방식

노드가 뛰어난 성능을 발휘할 수 있는 이유는 크게 비동기 이벤트 기반 아키텍쳐구글의 V8 자바스크립트 엔진을 이용했기 때문이라고 말할 수 있다. V8 자바스크립트 엔진에 대한 설명은 생략한다. 

 

쓰레드 기반 vs 비동기 이벤트 기반

지금까지 대부분의 애플리케이션들은 Blocking I/O를 사용하였고, 이 때문에 멀티 쓰레드를 사용할 수 밖에 없었다. 멀티 쓰레드는 개발자 입장에서 직관적이고 멀티 태스킹을 위해 어쩔 수 없는 선택이지만, 네트워크에서 동시에 대규모 요청을 동시에 처리하는 데에는 부적절하다. 멀티 쓰레드에 대해서는 아래에서 좀 더 자세히 다뤄보도록 한다.

 

Blocking I/O

일반적인 어플리케이션들은 대부분 Blocking I/O를 사용한다. Blocking I/O에 대해 자세히 설명하자면, "하나의 프로세스가 어떤 자원을 사용하고자 할 때 그 자원을 다른 프로세스가 점유하고 있다면, 그 프로세스가 그 자원의 사용을 끝마칠때까지 기다려야 한다는 것" 을 의미한다. 운영체제를 공부해봤다면 들어본 적이 있을 것이다.

먼저 애플리케이션이 운영체제의 커널에게 파일을 읽기 위해 시스템 콜이라는 형태로 요청을 보낸다. 커널은 파일을 읽기위한 동작을 수행하기 시작하고 애플리케이션은 커널이 파일을 다 읽을 때까지 기다려야 한다. 일반적으로 이 상태를 애플리케이션이 Blocked 되었다고 표현하며 이 시간 동안 실제로 애플리케이션은 아무것도 하지 않은 상태가 된다.

 

멀티 쓰레드

일반적으로 하나의 프로세스가 하나의 요청에 대해 대응하고 그 일을 처리하게 되는데, 만약 웹 서버와 같이 다수의 요청이 들어온다면 멀티 쓰레드라는 개념을 사용할 수 밖에 없게 된다.

멀티 쓰레드는 말 그대로 쓰레드가 동시에 여러개 실행되어 요청을 처리한다는 개념이다. 이것은 CPU의 시분할이라는 개념으로 설명될 수 있다. 시분할 개념은 CPU를 여러 프로세스 또는 쓰레드가 시간을 나누어 동작하도록 함으로써 마치 CPU를 공유하여 사용하는 것과 같은 효과를 낸다. 

그림의 첫 번째 경우는 시분할을 이용하지 않고 싱글 쓰레드로 요청을 처리할 때의 모습이다. 먼저 들어온 요청을 먼저 처리하고 이 요청에 대한 처리가 끝날 때까지 기다린 후에 다음 요청을 처리하는 식이다. 따라서 먼저 요청한 작업이 먼저 응답 받을 수 있다.

두 번째 경우는 단순한 프로세스 스케줄링을 적용한 경우로 특정 쓰레드가 일정 시간만 CPU를 사용할 수 있도록 되어있다. 이 경우 프로세스 C가 가장 늦게 요청을 했음에도 불구하고 가장 먼저 응답을 받을 수도 있음을 보여주고 있다.

CPU를 사용할 수 있는 시간이 더 적게 설정 된 세 번째 경우에도 마찬가지로 먼저 요청이 온 A보다 B가 먼저 작업을 마치고 응답을 할 수도 있는 모습을 보여주고 있습니다. 

 

쓰레드로 인해 발생하는 문제

멀티 쓰레드를 통해 복수의 요청으로 인해 발생하는 공유 문제는 해결할 수 있었다.

CPU 사용에 스케줄링을 통해 멀티 쓰레딩을 하더라도 하나의 CPU를 다수의 쓰레드가 사용하고자 한다면, 결국 CPU 자체도 하나의 자원이기 때문에 여러 쓰레드들이 CPU를 점유하기 위해 기다릴 수 밖에 없다는 점은 근본적인 문제이므로 제외시키자. 그렇다면 쓰레드 기반으로 동작할 때 발생하는 실질적인 문제점은 무엇일까?

먼저 Blocking I/O 자체가 발생시키는 쓰레드 지연에 대한 문제이다. 더 쉽게 이야기하면 I/O 요청을 하고 응답이 올때까지 아무것도 하지 않고 시간을 낭비하는 문제이다.

다음으로 스케줄링을 위한 처리 시간과 문맥 전환(Context switch) 비용이 발생한다는 점이다. 즉 쓰레드를 분배하여 사용하기 위해 사용되는 스케줄링 그 자체도 CPU를 이용한 연산이 필요한 작업이고, 쓰레드 간의 전환을 위해서는 전환 하기 직전의 쓰레드를 나중에 복귀시킬 때를 대비하여 그 상태를 저장해두어야 하는데, 이 또한 CPU를 이용한 연산이 필요한 작업이다. 따라서 쓰레드가 많아질수록 문맥전환에 따른 성능 저하가 발생할 수 있는데, 이 때문에 쓰레드들은 별도로 관리하거나, 더 작은 단위로 쪼개어 VM 등으로 실제 쓰레드로 분배하는 방식 같은 대안이 등장하고 있다. 이러한 방식들은 문맥 전환에 드는 시간이 네이티브 CPU 쓰레드를 사용하는 것 보다 더 적다.

 

싱글 쓰레드와 이벤트 기반의 비동기 I/O 처리

노드는 이러한 문제들을 싱글 쓰레드와 이벤트 기반의 I/O 처리로 해결하고 그 성능을 끌어올릴 수 있도록 하는 비동기 프로그래밍 모델을 제공해주고 있다.

싱글 쓰레드가 가진 노드는 I/O 작업이 시작되면 I/O 작업 처리에 대한 응답을 기다리지 않고, 바로 다음 작업을 실행해버린다. 대신 I/O 작업이 종료되면 이벤트를 발생시키고, 이 이벤트는 해당 프로세스의 이벤트 큐에 등록되게 된다. 노드로 개발된 프로세스는 이 이벤트 큐에 등록된 새로운 이벤트를 감지하여, 해당 이벤트 시 수행되어야 할 작업을 실행하게 된다.

 

이벤트 루프

이벤트 루프(Event Loop)라는 것은 작업을 요청하면서 그 작업이 완료되었을 때 어떤 작업을 진행할 지에 대한 콜백 함수를 지정하여 동작이 완료되었을 때 해당 콜백 함수를 실행되는 방식의 동작 방식을 말한다.

만약 클라이언트가 웹 서버에 HTTP 형식으로 요청을 하게 되면 서버에는 이벤트 루프가 계속 돌고 있다가 이를 감지하고 알맞은 작업을 워커 쓰레드를 생성하여 실행한다. 이 때 이벤트 루프는 해당 워커 쓰레드가 작업을 마친 뒤 그 결과와 함께 응답할 때까지 기다리는 것이 아니라 바로 루프로 복귀하여 다른 요청을 기다리게 된다.

즉 이벤트 루프는 어떤 요청이 발생하면 그 작업에 대해 쓰레드 실행만을 일으킬 뿐이다. 이 후 작업을 할당 받았던 해당 쓰레드가 모든 작업을 마치면 미리 전달받은 콜백 함수를 실행하도록 이벤트 루프로 응답하게 되며 이벤트 루프는 이것을 실행하여 클라이언트에게 결과를 응답해준다.

 

Node.js의 특징

지금까지 노드의 가장 큰 특징 중 하나인 이벤트 기반의 비동기 처리 방식에 대해 알아보았다. 하지만 이것만으로는 노드에 매력을 알 수 없다. 왜냐하면 노드는 이벤트 기반의 비동기 처리 방식이라는 특징외에도 다양한 매력을 가지고 있는 프로그래밍 언어이다. 특히 자바스크립트를 기반으로 하고 있다는 점과 이벤트 기반의 프로그래밍 모델, 확장성 있는 모듈 구조는 확실히 노드를 더욱 더 매력적으로 만들어주는 요소들이다. 이 특징들에 대해 좀 더 자세히 설명하도록 하겠다.

기존의 웹 애플리케이션에서 프론트 엔드를 구성하는 대부분이 자바스크립트였던 것을 생각하자. 노드를 사용한다는 것은 이제 백엔드도 자바스크립트를 이용하여 개발할 수 있다는 것이다. 

다시 말해 프론트 엔드와 백엔드가 자바스크립트라는 하나의 언어로 통일된다는 것이다. 이것은 단순히 개발 언어의 통일만을 의미하지 않는다. 하나의 개발 언어를 사용하게 되면 프론트 엔드와 백엔드가 좀 더 긴밀한 구조로 설계될 수 있고, 같은 프로그래머가 양쪽 모두를 개발할 수 있다. 따라서 개발 단계에서나 유지보수 단계에서 시간, 인력을 줄일 수 있으며 이로 인해 코드 통합이 쉬워지게 된다. JSON과 같은 공통 데이터 포맷도 프론트엔드와 백엔드 사이에서 쉽게 사용될 수 있다.

 

이벤트 기반의 프로그래밍 모델

이번에는 이벤트 기반의 비동기 처리 방식으로 인해 발생하는 성능 향상에 대한 장점이 아닌 프로그래밍 모델 자체에 대해 언급한다. 

이벤트 기반의 프로그래밍 모델이 처음일 수도 있겠지만 사실 근 몇 년 동안 프론트엔드 개발에서는 jQuery라는 자바스크립트 라이브러리가 널리 쓰이고 있었다. 이 jQuery가 이벤트 기반의 비동기 처리 방식으로 개발을 할 수 있도록 해주는데, 이러한 jQuery에 익숙한 개발자들에게는 이벤트 기반의 프로그래밍 모델을 가지고 있는 노드가 매력적으로 느껴질 것이다.

 

NPM을 통한 다양한 확장 모듈들

노드는 확장성이 뛰어난 모듈 구조를 가지고 있는데, 이를 통해 기본적으로 제공되는 모듈이외에도 다양한 확장 모듈을 설치하여 사용할 수 있다. 게다가 NPM이라는 설치 관리자를 통해 확장 모듈을 쉽게 설치할 수 있도록 되어 있다. NPM을 통한 다양한 기능을 가진 확장 모듈들은 노드의 보급에 큰 영향을 끼쳤으며, 앞으로도 훌륭한 확장 모듈들이 쏟아져 나올 것으로 보인다. 

728x90

'프로그래밍 공부 > Node.js' 카테고리의 다른 글

노드의 모듈  (0) 2020.01.21