본문 바로가기

node.js (OctoberSkyJs)

[node.js 따라배우기] Node v0.6 릴리즈 내용


node.js가 드디어 0.6 stable 버전을 릴리즈 했네요. 마침 식탁에 앉아서 입에 빵 물고 우물대다가, 라이언 달의 트윗을 듣고(?)는 접속해서 대충 살펴봤습니다.

우선 v0.6은 node.js의 세 번째 안정(stable)버전입니다. (참고로, node도 근래의 version numbering 추세인 홀수는 개발 버전, 짝수는 안정 버전이라는 컨벤션 룰을 따르고 있습니다.)  


v0.4과 v0.6의 주요 차이점


1. 소켓용  I/O Completion Ports를 이용한 네이티브 윈도우즈 환경 지원 

  - MS의 지원을 받아서 Native API를 지원하는 node.js를 만들고 있다더니 드디어 만들어 냈네요. 속도가 꽤 올랐습니다. 이건 뒤쪽에서 다루겠습니다. 그리고, MS의 지원을 받았다는 게 뭔가 했더니  I/O Completion Ports기술이더군요. 

 I/O Completion Ports는 멀티 프로세스 시스템에서의 복수개의 비동기 I/O 요청을 처리하기 위한효율적인 스레드 모델을 제공하는 기술입니다. 하나의 프로세스가 하나의 I/O Completion port를 만들면, 시스템은 요청들을 처리하기 위한 하나의 연관 큐(queue) 오브젝트를 만듭니다.  해당 큐는 오로지 요청들을 처리(서비스)하는 것이 목적입니다. 즉, I/O 요청을 받고나서 스레드를 생성하는 것이 아니라 미리 할당받은 스레드 풀과 결합되어 있는  I/O completion ports를 사용함으로써 더 빠르고 효과적으로 더 많은 숫자의 동시성을 가진 비동기 I/O를 다를수 있습니다... 라는 군요.

 
2. 멀티 프로세스간의 로드 밸런싱 기능 통합 (문서)
- Node는 기본적으로 싱글 프로세스에 싱글 스레드로 동작하게 되어있습니다. 따라서 멀티코어 시스템의 장점을 살리고 부하를 분산하기 위해서 node 인스턴스들을 클러스터(cluster)로 구성할 필요가 생길 수 있습니다. 다음은 간단한 예제코드입니다. 

var cluster = require('cluster');
var http = require('http');
var numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  // Fork workers.
  for (var i = 0; i < numCPUs; i++) {
    cluster.fork();
  }

  cluster.on('death', function(worker) {
    console.log('worker ' + worker.pid + ' died');
  });
} else {
  // Worker processes have a http server.
  http.Server(function(req, res) {
    res.writeHead(200);
    res.end("hello world\n");
  }).listen(8000);
}
위와 같이 서버를 띄우면 worker들이 이제 8000번 포트를 공유하게 됩니다. 

% node server.js
Worker 2438 online
Worker 2437 online

 
3. Node 인스턴스간에 더 나은 IPC 지원
* IPC : interprocess communications, 프로세스간 통신

 - spawn()으로 만들어진 node 인스턴스들, 그리고 일반적인  ChildProcess 프로세스간에 통신 하는 방법을 개선했습니다. 
 child.send(message, [sendHandle]) 로 보내고  'message'이벤트로 자식 프로세스에서 잡아내는 방식입니다. 예제를 보겠습니다. :)

다음은 부모쪽.

var cp = require('child_process');

var n = cp.fork(__dirname + '/sub.js');

n.on('message', function(m) {
  console.log('PARENT got message:', m);
});

n.send({ hello: 'world' });

그리고, 아래는 sub.js라고 가정합니다. 

process.on('message', function(m) {
  console.log('CHILD got message:', m);
});

process.send({ foo: 'bar' });  
채널을 이용해서 이렇듯 메시지를 주고 받습니다. 
다만, 유의점으로는 각 자식 노드들은 각각 새로운 V8 인스턴스가 됩니다. 각각의 자식 노드를 만들때마다 대략 기동시간 30ms에 메모리 10M가 가량이 필요하다는 이야깁니다. (이건 크롬 브라우저에서 탭 늘릴때도 거의 동일합니다) 즉, 자식 노드를 생성하는데는 한계가 있다는 이야기가 되겠습니다. :)


3. 커맨드라인 디버거 개선
(문서)

  - v0.4.1.2에 비해서는 대폭 개선되었습니다. 표현식과 변수의 값이 어떻게 바뀌는지 살펴보는 watchers 기능을 비롯하여 Stepping, Breakpoints, Info, Execution control 들등 훨씬 나아졌습니다... 라고는 하지만 기존 디버깅이 원체 볼품없었어서 뭐라 하긴 좀 힘들긴 하네요. -,-; 살짝 코드나 한 번 볼까요?

현재 가정은 myscript.js 파일안에 3번째 줄에 실제로 debugger; 라고 타이핑 해 놓았다는 가정입니다. (네.. 그렇습니다. 이클립스의 마우스로 행을 찍어서 실행하는 방식은 이런 식에 비하면 거의 SF 수준이죠 -,-)

다음은 myscript.js 파일

// myscript.js
x = 5;
setTimeout(function () {
  debugger;
  console.log("world");
}, 1000);
console.log("hello");

아래는 debugging 방법 주의! (복잡해 보이면 그냥 훅~ 지나가세요.)

% node debug myscript.js
< debugger listening on port 5858
connecting... ok
break in /home/indutny/Code/git/indutny/myscript.js:1
  1 x = 5;
  2 setTimeout(function () {
  3   debugger;  // 여기서 멈춥니다! 왜? 디버깅 모드로 실행했으니까!

debug
> cont < hello
break in /home/indutny/Code/git/indutny/myscript.js:3 1 x = 5; 2 setTimeout(function () { 3 debugger; 4 console.log("world"); 5 }, 1000); debug> next break in /home/indutny/Code/git/indutny/myscript.js:4 2 setTimeout(function () { 3 debugger; 4 console.log("world"); 5 }, 1000); 6 console.log("hello"); debug> repl Press Ctrl + C to leave debug repl > x 5 > 2+2 4 debug> next < world break in /home/indutny/Code/git/indutny/myscript.js:5 3 debugger; 4 console.log("world"); 5 }, 1000); 6 console.log("hello"); 7 debug> quit %

 
4. 압축을 위한 zlib  바인딩 내장

아래와 같은 식으로 모듈에 접근합니다.
 
var zlib = require('zlib');
다음은 파일스트림을 zlib스트림으로 읽어서 압축과 해제 파이프라인 처리하는 예제입니다.

var gzip = zlib.createGzip();
var fs = require('fs');
var inp = fs.createReadStream('input.txt');
var out = fs.createWriteStream('input.txt.gz');

inp.pipe(gzip).pipe(out);
압축/해제는 다음과 같은 식으로 간단하게 처리 가능합니다.

var input = '.................................';
zlib.deflate(input, function(err, buffer) {
  if (!err) {
    console.log(buffer.toString('base64'));
  }
});

var buffer = new Buffer('eJzT0yMAAGTvBe8=', 'base64');
zlib.unzip(buffer, function(err, buffer) {
  if (!err) {
    console.log(buffer.toString());
  }
});
유의사항은 zlib 인코딩은 비용이 많이 들기 때문에 유의해야 한답니다. 속도/메모리/압축에 대한 트레이드 오프(tradeoffs)에 대해서는  레퍼런스를 참고하세요.


기타등등...

윈도우즈 지원을 위해서 코어 아키텍처를 많이 손봤다고 합니다. 그러면서 유닉스 시스템에서의 성능을 저하시키게 되는 것이 아닐까 걱정했다는 군요. 다행히도 그렇게 되진 않았다고 하네요.

v0.4.12 (linux)v0.6.0 (linux)
http_simple.js /bytes/1024 5461 r/s 6263 r/s
io.js read 19.75 mB/s 26.63 mB/s
io.js write 21.60 mB/s 17.40 mB/s
startup.js 74.7 ms 49.6 ms

http와 io 벤치는 높을 수록 좋고, startup은 낮을 수록 좋다고 하니까 전체적으로는 개선되었다고 봐야하겠네요.

3개의 부하발생 머신으로 10 gigabit Ethernet (10 Gbit/s) 네트워크 환경에서 600개의 클라이언트로 접속하는 테스트를 했을 때의 결과랍니다.

다음은 윈도우즈 환경에서의 테스트 결과입니다.

v0.4.12 (windows)v0.6.0 (windows)
http_simple.js /bytes/1024 3858 r/s 5823 r/s
io.js read 12.41 mB/s 26.51 mB/s
io.js write 12.61 mB/s 33.58 mB/s
startup.js 152.81 ms 52.04 ms

윈도우즈 환경에서의 성능이 엄청나게 뛰었는데요, 0.4때는 Cygwin을 이용했지만 0.6은 native API를 이용했기 때문이랍니다. (뿌듯하겠어요. 라이언 달 :)
그래도 아직 중간쯤 정도 진행된 수준이고 개선의 여지가 남아있답니다. npm 이 native로 돌지 않기 때문에, 여전히  MS Visual Studio 환경에서 node 모듈들을 설치/동작시키는데는 애로점이 많은것 같습니다.

0.4과 0.6 사이에서 바뀐 API들은 아래에서 확인가능합니다.

코어 API는 거의 건드리지 않았기때문에 큰 고통은 없을 거랍니다. 릴리즈 주기를 좀 더 드라마틱하게 조여보겠다며 다음 릴리즈는 내년 1월을 예정한답니다. 궁극적으론 구글 크롬과 V8 자바스크립트 엔진의 6주 릴리즈 사이클과 맞추길 희망한답니다.

개인적으로는 윈도우즈 환경에서 node.exe 프로세스가 죽지 않는 버그(?)가 해결되어서 좋습니다. :) 
 
마지막으로 아래는 공식 사이트의 요약내용입니다.

2011.11.04, Version 0.6.0 (stable)

  • print undefined on undefined values in REPL (Nathan Rajlich)
  • doc improvements (koichik, seebees, bnoordhuis, Maciej Małecki, Jacob Kragh)
  • support native addon loading in windows (Bert Belder)
  • rename getNetworkInterfaces() to networkInterfaces() (bnoordhuis)
  • add pending accepts knob for windows (igorzi)
  • http.request(url.parse(x)) (seebees)
  • #1929 zlib Respond to ‘resume’ events properly (isaacs)
  • stream.pipe: Remove resume and pause events
  • test fixes for windows (igorzi)
  • build system improvements (bnoordhuis)
  • #1936 tls: does not emit ‘end’ from EncryptedStream (koichik)
  • #758 tls: add address(), remoteAddress/remotePort
  • #1399 http: emit Error object after .abort() (bnoordhuis)
  • #1999 fs: make mkdir() default to 0777 permissions (bnoordhuis)
  • #2001 fix pipe error codes
  • #2002 Socket.write should reset timeout timer
  • stdout and stderr are blocking when associated with file too.
  • remote debugger support on windows (Bert Belder)
  • convenience methods for zlib (Matt Robenolt)
  • process.kill support on windows (igorzi)
  • process.uptime() support on windows (igorzi)
  • Return IPv4 addresses before IPv6 addresses from getaddrinfo
  • util.inspect improvements (Nathan Rajlich)
  • cluster module api changes
  • Downgrade V8 to 3.6.6.6