Node.js 내가 쓰기로 선택한 이유

 IT  Comments Off on Node.js 내가 쓰기로 선택한 이유
Jan 152015
 

퍼온 글

https://vinebrancho.wordpress.com/2014/03/24/node-js-%EB%82%B4%EA%B0%80-%EC%93%B0%EA%B8%B0%EB%A1%9C-%EC%84%A0%ED%83%9D%ED%95%9C-%EC%9D%B4%EC%9C%A0/

 

개인 프로젝트에서 RESTful API를 구현하기 위해서 어떤 서버사이드 플랫폼을 사용할까 고민하다가 node.js 를 선택했다. 2000년초반, PC 웹이 한창 성장하고 있을 때만 해도 서버사이드 플랫폼으로는 asp, jsp, php 정도였던 걸로 기억한다. 그 때 취미삼아 웹사이트를 만들어본 경험이 있는데 고민했던 게 위 3가지 정도였다^^; 그러나 지금은 선택할 수 있는 서버 사이드 플랫폼이 상당히 많아졌다. 언어별로 대표적인 플랫폼을 나열해보았다.

  • Javascript : node.js
  • Ruby : Ruby on Rails (+ EventMachine for event loop)
  • Pyhon : Django (+ Twisted for event loop), Pyramid, Bottle, Nimble
  • Java : Play, Spring, Vert.x
  • PHP : cakePHP, Code igniter
  • Scala : Play
  • Erlang : inets, misultin

서버사이드 개발자들에겐 익숙한 것들이겠지만, 이번에 서버사이드 플랫폼을 들여다보기 시작한 나에게는 대부분이 낯설었다. 위의 모든 플랫폼에 경험이 있다면 각각 장, 단점을 기반으로 내 서비스에 적합한 것을 쏙 하나 집어 낼텐데, 나는 그렇지 못하기 때문에 내 나름의 기준에 맞는 플랫폼을 몇개 추려내고 그 중 하나를 선택하기로 했다.

내가 선택하는 서버사이드 플랫폼은 다음을 만족해야 했다.

  • 시간이 많지 않기 때문에 당장 쓸수 있는 언어 기반이어야 한다.
  • 소셜 서비스 성격상 수많은 사용자의 동시 접속과 요청을 빠르게 처리할 수 있어야 한다.
  • 이를 위해 제약된 하드웨어 인프라에서 확장성이 좋아야 한다.
  • 개발 시간을 단축하기 위해서 유틸리티 성격의 확장 모듈들도 많아야 한다.
  • 문서화 및 개발자 커뮤니티가 활성화되어 있어야 한다.

이렇게 기준을 정하고 나니 node.js와 django 두개로 압축할 수 있었다. 대부분은 1번 기준으로 제외되었다 -_-;; 그리고 twitter나 github가 Ruby on Rails(RoR)를 사용하고 있어서 한번 써보고 싶기는 했으나 Ruby 언어를 익혀야 하니 일단은 제외시켰다. 자, 그럼 node.js와 django 중 어떤 걸 할까. 2009년에 개발자 Ryan Dahl에 의해 공개된 후 실리콘밸리 개발자들을 열광하게 만들면서 급성장하게 된 node.js. Yahoo, VMWare, Microsoft, LinkedIn 같은 쟁쟁한 회사들이 쓰고 있는 node.js. 현재 5만개가 넘는 모듈을 갖고 있는 node.js. 직접 경험해보고 싶었다. 얼마나 좋길래. 그래서node.js를 개인 프로젝트의 서버사이드 플랫폼으로 쓰기로 결정했다.

지금부터는 node.js의 기술적인 특징에 대해 몇가지 나누고자 한다. 먼저 node.js는 공식사이트에서 다음과 같이 소개되고 있다.

Node.js is a platform built on Chrome’s JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.

– nodejs.org

이 문장 안에 사실 node.js에 대한 주요 특징들이 다 포함되어 있는 거 같다. node.js는 javascript 코드를 실행시키고, event driven 및 non-blocking I/O 모델을 사용한다. 그 결과 확장성 있고 데이터 중심의 실시간 네트워크 어플리케이션 개발을 가능하게 해준다라고 한다. 추상적으로 이해될수도 있는 부분인데 특징을 하나씩 살펴 좀 구체적으로 이해를 해보자.

Javascript 언어 실행

node.js를 만든 Ryan Dahl은 개발 언어로 javascript를 선택했다. 히스토리를 보면 C, Rua를 고려하다가 몇개월 뒤에 javascript가 적합하다고 판단하였다고 한다[1]. 아마도 javascript 언어가 가진 특징 때문이지 않을까 싶다. javascript 는 언어 자체적으로 event driven 모델을 이미 지원한다[2]. 이 때문에 클라이언트 사이드인 웹페이지에서 버튼을 하나 클릭하면 바로 화면에 어떤 작업이 수행된다. 서버 사이드로 예를 들면 http 요청이 한개 들어오면 바로 연결을 맺으면서 어떤 작업을 수행시키게 될 것이다. 잠시 뒤에서 좀 더 다루겠다. 또한 javascript는 내부 함수로써 외부 함수의 변수에 접근할 수 있는 closures란 기능을 지원한다[3]. 콜백함수를 파라미터로 지정하는 event driven 모델에서 매우 유용한 기능이다. 보통 함수는 자신의 scope에 지정된 변수만 접근 가능하기 때문에 콜백 함수로 사용된 어떤 함수가 외부의 변수에 접근하려면 콜백 함수를 호출하는 쪽에서 반드시 사용할 해당 변수를 지정해줘야 한다. closure 덕분에 javascript 코딩시 이런 것을 신경쓰지 않아도 된다.

node.js는 CommonJS 라는 스펙을 따르고 있다[4]. CommonJS는 특정 코드가 아니라, 웹브라우저 외 서버사이드나 독립적인 애플리케이션으로 사용하기 위해 필요한 기능을 정의한 스펙이다.node.js 로 개발하다 보면, module, exports 그리고 require 같이 브라우저용 자바스크립트 언어에는 없는 지시어들을 보게 될 텐데 그러한 것들은 CommonJS 인한 것으로 보면 된다.

node.js 이전에도 helma, appjet, jaxer 등 javascript 기반 서버사이드 플랫폼이 있었으나 활성화가 되지 못했다. 이유가 궁금하여 구글링을 해보니 node.js 가 성공한 이유는 javascript 언어에 대한 인식 변화, javascript engine의 성능 향상 그리고 Blocking I/O을 제거했기 때문이라는 분석글을 봤다[5]. 반대로 이전에 서버사이드 플랫폼들은 이 세가지를 갖고 있지 못했기 때문에 활성화가 되지 않았다는 건데 설득력이 있다. 첫째, 웹이 성장하면서 javascript 언어의 가치도 함께 올라갔고 그만큼 개발자들도 늘어났다. 이제 더이상 javascript 언어를 부정적으로 보거나 사라질 언어로 보질 않는다.둘째, 구글이 javascript 코드를 동적으로 컴파일하여 기계어로 바꾸는 V8 엔진을 개발하면서 javascript 실행 성능이 크게 좋아졌다. 그리고 V8이 컴파일된 기계어 코드를 캐슁(caching)하기 때문, 동일한 로직을 많이 수행할 수 밖에 없는 서버사이드에서는 더욱 효과적일 것이다.셋째, Ryan dahl이 unix/linux의 시스템 레벨에서 blocking I/O 처리되던 라이브러리들을 node.js에서 사용될 때 non-blocking I/O로 동작하도록 바꾸면서 서버 확장성의 문제를 해결한 것이다. non-blocking I/O에 대해서는 잠시 뒤에서 다루겠다.  아무튼 이 3박자가 잘 들어맞아 현재의 node.js 가 많은 개발자와 회사들로부터 사랑을 받는게 아닌가 싶다.

Single thread event-driven 그리고 Non-blocking I/O 모델

event driven은 event 기반, 즉 특정 사건이 발생할 때 작업이 수행되는것을 의미한다. UI를 가지면서 유저와 상호작용해야 하는 프로그램에선 유저가 언제 터치 같은 입력(즉, event)을 줄지 모르기 때문에 event driven 모델 사용이 일반적이다. javascript 언어가 event-driven 모델기반이기 때문에,node.js 는 javascript를 채택함으로써 개발자들이 서버사이드의 모든 작업을 event-driven 모델로 개발하도록 한다.실제로 node.js 가 기본으로 제공하는 javascript API 원형들을 보면 파라미터로써 callback을 입력하도록 요구하고 있다[6].

전통적인 서버사이드 개발 모델은 적절한 표현일지 모르겠지만 thread driven 모델이었다. 즉,  서버에 요청이 들어오면 서버는 이를 처리하기 위해 매번 별도의 thread를 생성했었다. apache같은 웹서버가 이런 모델인데, 이유는 POSIX 기반 함수들이 blocking I/O 방식으로 file을 엑세스한다라는 사실에서 찾을 수 있다. blocking I/O 방식은 filesystem, database 리소스 접근시 결과가 리턴될 때까지 코드 수행을 멈추고 다른 작업이 수행되지 못하게 하기 때문에, 하나의 thread로는 추가적으로 들어오는 클라이언트 요청을 처리할 수 없다. thread driven 모델은 클라이언트 별 thread로 인해 메모리 사용량이 증가하는 문제와 컨텍스트 스위칭 비용이 발생하는 문제가 있고 이는 클라이언트 수가 증가할수록 각각 확장성과 반응성을 떨어뜨리게 한다. Ryan Dahl이 2009년 JSConf에서 apache vs. nginex(event driven모델 서버중 하나) 성능 벤치마크 자료를 통해 수많은 클라이언트가 동시 요청시 event driven 모델이 초당 더 많은 요청을 받을 수 있고 메모리를 훨씬 덜 사용한다고 소개하기도 했다[7]. thread driven 모델에서 확장성과 반응성을 올리려면 서버 하드웨어 스펙을 올리던지 여러대의 서버를 운영하여 적절히 로드밸런싱이 되도록 해야 한다. 물론 그에 따른 경제적 비용이 만만치 않겠지만 말이다.

node.js는 event loop과 non-blocking I/O를 통해 전통적인 서버 사이드 모델에서의 이러한 문제들을 해결한다. node.js 서버(하나의 프로세스)는 한개의 event-loop thread(보통 main thread)로 동작한다. v8과 그 위에서 동작하는 서버사이드 javascript 코드는 모두 event loop thread에서 동작한다. 그리고 이 thread가 모든 클라이언트 요청을 받고 처리한다. node.js는 javascript 언어에서 기본적으로 제공되지 않는 filesystem, network (tcp/udp, http/https)등 시스템 리소스에 서버사이드 개발자가 접근하도록 하기 위해서 v8의 interface[8]에 맞게 c++로 구현(<node.js>/src/*.cc javascript로 바인딩하여 콜백 파라미터를 가진 javascript API set을 제공(<node.js>/lib/*.js다[9]. blocking I/O 작업을 해야하는 리소스에 대해서는 thread pool을 이용해 node.js가 event loop thread가 아닌 별도의 thread에 그 일을 위임한다. blocking I/O 작업을 하는 thread의 작업이 끝나면, event loop thread가 이를 인지하고 v8을 통해 해당 리소스에 매핑되어 있는 javascript 콜백을 바로 호출한다. 즉, event loop thread에서 수행되는 javascript 컨텍스트에선 시스템 리소스 접근이 non-blocking I/O 처럼 동작하는 것이다. 아래 그림은 이런 flow를 잘 설명해주고 있다.

<source : https://www.udemy.com/blog/learn-node-js/>

기본적으로 node.js는 단일 event loop을 가진 thread로 동작하기 때문에 클라이언트 요청 증가에 대해 thread driven 모델보다는 좀 더 좋은 확장성(scalability)을 보인다. 그리고 별도의 thread에서 blocking I/O 작업이 처리되는 동안 event loop thread에선 다른 작업을 수행할 수 있으므로 동시에 발생하는 클라이언트 요청에 대해 좋은 반응성(responsiblity)을 보여줄 수 있다. 하지만 thread driven 대비 동시접속에 대한 요청당 처리시간이 빠르다고는 하지만 동시접속수가 많아질수록 반응성은 떨어질 수 밖에 없기 때문에 node.js 서버를 사용하더라도 여러개의 node.js를 띄워서 로드밸런싱을 해줘야 하지 않을까 싶다[10].

node.js를 들여다볼수록 node.js의 핵심은 blocking I/O로 동작하던 filesystem, network 기능들을 non-blocking I/O로 동작하도록 바꾼 것이란 생각이 든다. node.js는 javascript와 low level system 사이의 glue 역할을 하고 있는데 non-blocking I/O가 없었다면 지금의 node.js도 없지 않았을까. 시간이 될때 이부분을 소스레벨에서 hacking 해봐야겠다 ^^

node.js로 추천하지 않는 usecase

node.js는 한개의 event loop thread로 서버로의 모든 요청을 처리하기 때문에, 특정 요청을 처리하는 javascript 콜백에서 연산등의 cpu intensive한 작업을 오랫동안 한다면 그 시간만큼 다른 요청들은 대기할 수밖에 없다. 물론 cluster 모듈을 이용해서 멀티코어에서 코어 갯수만큼 동일한 node.js프로세스를 띄워서 다른 코어 위에 동작하는 node.js 프로세스로 요청을 분산되도록 할 수 있으나, 같은 tcp port 로 들어오는 클라이언트 요청을 여러 코어로 분산되도록 하는건 node.js 가 아닌 커널레벨의 정책에 따른 것이라 어떻게 동작할지 예측하기가 어려울 것 같다. (node.js의 현재 공식 stable version 0.10.26 까지는 커널이 몇몇 특정 worker 프로세스로만 요청을 보내는 문제가 발생하여, 0.11 버전 때부터 로드밸런스를 커널에게 맡기지 않고 node.js가 직접하기 위해서 cluster master 프로세스가 직접 모든 요청을 받고 자기가 생성한 worker 프로세스들에게 round robin (디폴트 정책)으로 분산을 해주는 패치가 적용됐다[11][12]. stable version 0.12부터 해당 내용이 적용될 것이다.). 더군다가 클라이언트 요청이 몇 천, 몇만 건으로 들어오면 이런 류의 cpu-intensive 한 서비스는 제대로 처리해주기 어려우므로 node.js에 적합하지 않다. 그러나 굳이 꼭 node.js를 써야 한다면.. cpu intensive 작업을 c++ native 모듈을 만들되 node.js의 non-blocking 인터페이스 (uv_*함수들)나 v8의 멀티쓰레딩 인터페이스를 활용하여 v8에 binding한다면 별도의 쓰레드로 cpu intensive한 작업을 수행시키는 것이므로 괜찮지 않을까 싶다. node.js로 흔히 사용되는 usecase는 CRUD 작업을 주로 하는 RESTful 서비스나 notification push 같은 real time web application에 유용하다고 한다. realtime 지원은 node.js의 socket.io 모듈을 이용하면 쉽게 구현이 가능하다. 다음은 node.js의 usecase 관련 글이다.

http://nodeguide.com/convincing_the_boss.html

http://www.toptal.com/nodejs/why-the-hell-would-i-use-node-js

node.js에서 많이 사용되는 주요 모듈

node.js 서버 개발을 시작한지 며칠되지 않아서 다양한 모듈을 사용해보질 못했다. 하지만 현재 express 모듈로 RESTful API처리하는 부분을 코딩해보니 헉,, 그 편리함에 놀라고 있다. 모듈 개발자들에게 고마운 마음이;; ㅎㅎ. 계속 개발을 하다보면, 내 삶을 더욱 편하게 만들어줄 여러 모듈들이 있을거라 생각하는데 혹시나 없다면 직접 만들어서 npm 모듈로도 공개해봐야겠다. 참고로 현재 공개된 npm 모듈은 5만개이상이다.

https://www.npmjs.org

https://nodejsmodules.org/tags/build

http://www.queness.com/post/16219/29-nodejs-frameworks-for-fast-javascript-development

 

node.js 튜토리얼

node.js의 API는 공식사이트의 documentation에 잘 정리되어 있다. 그 외 아주 기본적인 내용들은 아래 사이트를 참고하면 될 것 같다.

http://www.nodebeginner.org/index-kr.html

http://howtonode.org/

http://pismute.github.io/nodeguide.com/beginner.html

http://nodeschool.io/

node.js 적용 사례에 대한 글

http://nodejs.org/industry/

https://github.com/joyent/node/wiki/Projects,-Applications,-and-Companies-Using-Node

http://readme.skplanet.com/wp-content/uploads/Tech_Planet_2013_baek.pdf

https://queue.acm.org/detail.cfm?id=2567673

http://venturebeat.com/2011/08/16/linkedin-node/

마치며…

단말용 tizen의 C++기반 web runtime을 개발하면서 주로 테스트 용도로만 javascript 언어를 사용했었다. 그 덕분에 node.js 로 javascript 코딩을 하는데 언어적인 거부감이나 어려움은 없다. 그리고 초반부에 서버사이드 플랫폼을 선정하는 내 나름의 기준들을 node.js 가 잘 만족시켜주는 것 같다. 에효 드디어 정했다. (ps. 혹시나 제가 쓴 글 중에 잘못된 부분이 있다면 주저말고 지적해주세요.)

< References >

[1] http://www.theregister.co.uk/2011/03/01/the_rise_and_rise_of_node_dot_js/?page=3
[2] http://www.launchacademy.com/codecabulary/learn-javascript/event-driven-asynchronous-callbacks
[3] http://javascriptissexy.com/understand-javascript-closures-with-ease/
[4] http://en.wikipedia.org/wiki/CommonJS
[5] http://programming.oreilly.com/2011/06/node-javascript-success.html
[6] http://nodejs.org/api/addons.html
[7] http://www.scribd.com/doc/23801896/Node-js-JSConf-2009
[8] https://developers.google.com/v8/embed
[9] http://nodejs.org/api/
[10] http://zgadzaj.com/benchmarking-nodejs-basic-performance-tests-against-apache-php
[11] http://strongloop.com/strongblog/whats-new-in-node-js-v0-12-cluster-round-robin-load-balancing/
[12] https://github.com/joyent/node/commit/e72cd41?__utma=108008000.708719925.1395624487.1395790930.1395792961.3&__utmb=108008000.2.10.1395792961&__utmc=108008000&__utmx=-&__utmz=108008000.1395792961.3.3.utmcsr=google%7Cutmccn=(organic)%7Cutmcmd=organic%7Cutmctr=(not%20provided)&__utmv=-&__utmk=101010433

 

 Posted by at 10:22 PM