Outsider's Dev Story

Stay Hungry. Stay Foolish. Don't Be Satisfied.
RetroTech 팟캐스트 44BITS 팟캐스트

Nodeconf 2012 참석기 : Day 4 #1

Nodeconf 둘째날입니다. 마찬가지로 튜피터호텔에서 아침식사를 먹고 10시정도에 알아서 아미고 극장에 모여있으니까 Nodeconf 둘째날이 시작되었습니다. 마찬가지로 발표자료는 nodeconf slides - 2012를 참고하길 바랍니다.


Streams
둘째날에 오전 세션은 스트림에 대한 주제였습니다. 스트림은 Node에서 좋은 기능중 하나라 최근에 많이 관심이 모아지고 있습니다.


Porting Node: The Journey from Lake JavaScript to The Strait of Lua - Tim Caswell
따로 안내가 없기는 했지만 이 세션은 사실 전 날 Plaforms에 포함된 세션이었던 것 같은데 발표시간에 Tim Caswell이 자리를 비워서 발표를 못하고 이 날 Streams부분에 끼어들었던 것 같습니다. 이 세션은 Tim이 node를 Lua로 포팅한 Lua로 바꾼 Luvit(Luauvjit)에 대한 발표였습니다. Luvit은 C 라이브러리인 libuv, http_parser, openssl, zlib는 그대로 사용하고 자바스크립트 엔진인 V8 대신 Lua 코드를 실행하기 위해서 LuaJit로 바꾸었습니다. 왜 포팅을 했는가 하면 LuaJit가 V8보다 가볍고 Lua는 코루틴을 가지고  fast FFI(Foreign Function Interface)가 내장되어 있습니다.


이어서 Luvit을 만들기 위한 과정을 설명해 주었습니다. 먼저 libuv 라이브러리를 공부하고 Programming in Lua 2nd Edition를 사서 모두 읽고 메일링 리스트등을 이용해서 Lua를 공부했습니다.(이런 해외 개발자들이 기술을 접근하는 방법을 보면 종종 놀라곤 합니다. 자신이 좋아하는 기술로 포팅하는게 아니라 포팅을 결정하고 언어를 공부한다니요! 이런 기술의 접근방법 차이가 큰 차이를 만들어 주는 것이겠지요.) 그리고 node API를 Lua로 만들 수 있는지 프로토타이핑을 했습니다. 그리고 실제로 libuv를 어떻게 바인딩 했는지 또 어떤 이슈가 있었는지를 설명했습니다.

이 작업 결과 다음을 배웠습니다.

  • Lua는 Node.js API 패턴과 호환성을 가질 수 있습니다.
  • LuaJit이 V8보다 적은 메모리를 사용합니다.
  • LuaJit에서 C코드를 호출하는 것이 V8보다 훨씬 빠릅니다.
  • 스크립트가 많아질수록 V8이 빨라지는 경향이 있습니다.
  • 언어가 지원하는 코루틴은 좋습니다.
  • JSON 스트리밍은 좋지만 Lua에선 LTIN이 더 낫습니다.
  • Node의 require의 시스템은 좋습니다.
마지막으로 오픈소스 협업은 무엇이든지 만들 수 있습니다. 각자 스크립트 언어를 선택해서 이러한 과정을 진행해 보기를 권했습니다.


Streams - Marco Rogers
Marco는 야머에서 일하는 개발자입니다. 꽤 유쾌해보이는 사람이었는데 스트림을 설명하면 질문이 많이 나올 것이라면서 질문을 먼저하라고 해서 다들 웃기기도 했습니다. 스트림은 특수한 이벤트 이미터로 증가하는 데이터를 노드에 알려주는 방법이라고 할 수 있습니다.


먼저 data 이벤트로 업로딩되는 파일을 계속 변수에 담아서 마지막에 파일로 쓴느 코드를 보여주었습니다. 이 메모리 버퍼링 방식은 효율적이지 못합니다. 스트림을 사용하면 연속된 데이터를 파이프할 수 있습니다. 그래서 이 코드를 WriteStream을 사용해서 바로 파일에 쓰는 예제를 보여주었습니다. 스트림이 더 좋은 이유는 지속적으로 이동하면서 더 적은 메모리를 사용하고 쓰루풋이 높은 데다가 부하를 조절할 수 있습니다. 이 두 예제의 성능을 비교해 보았는데 메모리 버퍼링을 사용하는 예제의 경우 최초 한번 업로딩을 했을 때 22메가 정도에서 업로딩을 시도할 때마다 4-6메가씩 계속 증가했지만 Stream을 이용한 예제에서는 19메가로 시작해서 23메가로 증가한뒤 여러번 시도해도 더이상 증가하지 않았습니다.(process.memoryUsage().rss로 측정했습니다.)

Marco는 직접 스트림을 작성해 보기를 권하며 실제 stream.js 소스도 그리 길지 않다는 점을 강조했습니다. 그러면서 stream을 상속받아서 만든 gzipstream.js를 직접 구현한 예제를 보여주었습니다. 이렇게 Stream은 데이터를 스트리밍하는 방법을 모듈화 하는 방법이며 데이터처리와 요청의 처리를 분리하도록 해주기 때문에 라이브러리를 작성하는 개발자라면 라이브러리 내에서 스트림지원을 고려하기를 권했습니다.


fast binary stream parsing - Felix Geisendorfer
이 세션은 Felix가 node-mysql을 개발하면서 얻은 경험을 공유하는 세션이었습니다. 데모를 보여주면서 mySQL 프로토콜을 분석하는 예제부터 시작했습니다. mySQL을 연결해서 파이프로 응답을 받았지만 읽을 수 있는 데이터가 아니라 data 이벤트를 통해 버퍼로 출력하니까 읽을 수 있는 데이터가 되었습니다. MySQL이 제공하는 클라이언트/서버 프로토콜 문서를 참고해서 어떻게 프로토콜의 패킷 헤더를 파싱하는 지를 직접 보여주었습니다. 버퍼로 받은 내용을 문서에 따라 필요한 길이만큰 계산에서 가져와서 의미있는 데이터로 파싱해서 출력해 주었습니다. 실제로 라이브코딩을 하면서 데모를 해주었기 때문에 꽤나 흥미로왔습니다.


성능이 libmysql에 비해서 절반정도로 느렸기 때문에 속도를 더 빠르게 하기 위한 시도를 시작했습니다. v8-profiler를 사용하고 dtrace를 사용하고 binary를 검색하면서 여러가지 방법을 구상했습니다. 그 결과 다시 작성하기로 결정하고 node-mysql 2.0을 작성했습니다. 2.0은 libmysql  클라이언트보다 훨씬 빠른 속도를 가지게 되었습니다. 성능을 향상 시키기 위해서 항상 현재의 퍼포먼스를 먼저 벤치마킹했습니다. 그리고 버퍼링을 사용하고 추상화와 함수호출을 이용했씁니다. 실제 벤티마킹했을 때 try...catch 구문은 성능에 영향을 미치지 않았고 커다란 switch문은 성능이 그다지 좋지 않았습니다. 이렇게 성능을 개선한 것을 보였지만 개선된 2.0보다 2배는 빠른 php 라이브러리와의 비교를 보여주며 아직도 개선할 점이 많이 남았음을 보여주었습니다.


streams are useful when writing javascript programs - maxogden
flow control 라이브러리를 보통 필요로 하는데 실제로 Stream이 flow control이다. MINASWAN (Matz is nice and so we are nice)라는 말을 보여주면서(제가 루비 개발자가 아니라 정확히 모르겠지만 루비쪽에서 사용하는 말인가 봅니다.) JIFASNIF(javascript is fun and so node is fun)라는 용어를 만들어서 관객들의 큰 호응을 얻었습니다. 이 세션은 maxogden이 만든 HTML5 API를 위한 스트림 라이브러이인 domnode에 대한 세션이었습니다.


클라이언트측에서 이벤트 기반에 I/O는 Ajax, WebSocket, File, Web Worker, 웹캠, 위치정보, 오디오등들이 있습니다. 이런 HTML5의 API를 사용한 예제와 node의 관련 스트림 모듈을 사용했을때 얼마나 코드가 간단한 지를 비교해서 보여주었습니다. 말을 다 알아듣지는 못했는데 클라이언트 소스에서 require를 쓰고 있는데 아마 browserity를 사용한 것으로 보입니다. 이렇게 함으로써 복잡한 HTP5 API를 on과 pipe로 대부분 해결할 수 있었습니다. 이렇게 만든 domnode를 소개했고 browserity가 클라이언트측 노드 라이브러리라면 domnode는 클라이언트측 노드 패턴이라고 설명했습니다. 크로스브라우징 문제는 어떻게 해결되나 하는 궁금증이 생기긴 했지만 사용해보진 않아서 머라 말하긴 어렵네요.


Streams at Scale - Matt Ranney
Matt은 Voxer의 CTO이고 Voxer는 아이폰/안드로이드의 워키토키앱을 서비스하는 회사로 초기부터 Node를 사용하고 있습니다. 사실 이사람 말은 많이 못알아먹었는데 발표자료도 공개하지 않았네요. 제대로 못알아들어서 그런지는 몰라도 실제 Voxer가 노드로 확장을 하면서 생긴 문제들을 공유해 줄 것처럼 했지만 먼가 추상적인 얘기만 하다가 끝난것 같은 기분입니다.


워키토키앱이기 때문에 동시접속 문제를 많이 겪을 것 같습니다. 동시접속을 해결하는 데 많은 얘기들이 있지만 실제로 대부분 잘 동작하지 않았고 동시접속수를 제한하는 것이 가장 좋은 해결책이고 인다운드 동시접속도 net모듈의 최대 연결수를 지정해서 제어했다고 얘기했습니다. 이런저런 얘기를 하다가 결국 제한하는 것 말고는 방법이 없다고 했습니다. 어려운 문제들은 실제로 무척 어렵기 때문에 세미나 등에서 이론적으로 얘기하는 것들로는 쉽사리 해결되지 않는답니다.


Debugging & Profiling
점심을 먹은 뒤 오후 시간은 디버깅과 프로파일링에 대한 세션이었습니다. 이번에는 전날 보다는 점심이 빨리나와서 점심먹고 좀 쉴 시간도 있었네요.


Paolo Fragomeni
Paolo는 Nodejitsu의 공동 창업자이면서 Node Firm의 공동 창업자라고 자신을 소개했습니다. 복잡도에 대해서 얘기를 하면서 모든 것은 문제가 일 을 수 있는데 사실 로깅이 가장 좋은 해결책이지만 로깅은 부하를 줄 수도 있다. 또한 top이나 topdump를 통해서 어떤 프로세스를 사용중이고 CPU 사용율등을 파악할 수 있지만 top이 다른 프로세스를 감시하기 때문에 부하를 줄 수 있어 문제의 소지가 있다.


이러한 부분에 대해서 전반적으로 해주는 것이 dtrace다. dtrace는 스크립트로 dscript를 사용해서 커널레벨에서 감시하면서 I/O call이나 system call을 확인할 수 있다. /usr/bin/iosnoop의 소스를 보여주면서 여러가지 설명을 해주었는데 이런 부분에 대해서는 잘 못알아듣겠더군요. dscript는 무엇을 찾는가를 나타내는 probe와 흐름을 제어하는 predicate, 실제 액션인 action 문의 3가지로 구성되어 있습니다. 뒤에도 dtrace 세션이 있어서였는지는 몰라도 dtrace에 대해서 뭔가 보여줄듯 하다가 끝나버린 세션이었습니다.


Dtrace, Node.js, and Flame Graphs - Dave Pacheco
Node는 시스템 프로그래밍을 하기 좋은 환경으로 성능이 좋고 유닉스를 추상화 했으면 이벤트기반의 아키텍쳐를 가지고 있습니다. 이 세션은 Dtreace에 대한 세션으로 올해 들어 Dtrace 얘기가 많이 나왔기 때문에 관심이 가는 세션이었습니다.

추측하지 말고 모든 것을 실제로 측정해 봐야 하는데 Dtrace는 데이터주도로 성능을 측정할 수 있으며 실시간으로 커널레벨과 어플리케이션 레벨을 모두 추적할 수 있는 데가가 추적하는 이벤트를 원하는만큼 확장할 수 있습니다. 오버헤드가 적으면서 안전하기 때문에 프로덕션레벨에서 사용하기에도 접학합니다. 성능문제에는 2가지가 있는데 비동기 문제와 동기 문제입니다. 보통 비동기 문제는 CPU와 관련된 것은 아니로 파일시스템이나 데이터베이스 혹은 아키텍쳐의 문제이고 동기 문제는 CPU를 많이 사용하는 문제로 보통 프로그램에 문제가 있는 것입니다.


비동기 작업을 측정하는 것은 비동기 작업의 지연시간을 보려는 것이고 system probe를 통해서 Dtrace로 측정해 볼 수 있습니다. 이후 restify 모듈로 만든 앱을 통해서 데모를 보여주었습니다. 이 어플리케이션에 ab를 통해서 요청을 계속해서 날리면서 dtrace -ln 'node*:::gc-start'로 시작하고 dtrace -ln 'nodeconf*:::' 명령어로 어플리케이션을 찾았습니다. nodeconf가 데모 앱의 이름인데 명령어는 빨리 받아적느라 일부 틀릴수도 있습니다. 그리고 restify-latency.d라는 dscript를 실행해서 데이터를 측정한 다음에 측정한 값을 보여주었습니다.

동기 작업을 측정할 때도 Dtrace를 사용할 수 있는데 이 경우에 동기작업이므로 대기하는 것이 아니라 직접 어떤 작업을 하고 있는 것이므로 무슨 작업인지를 찾아야 하는데 system probe로 찾을 수 있습니다. probe를 통해 모든 CPU를 추적해서 사람이 읽을 수 있는 형태로 보여줍니다. 마찬가지로 데모로 보여주었는데 dtrace -n profile-97'/pid == 63345/ { @[ jstack(150, 8192) ] = count() 명령어를 실행하니까 Dtrace의 인상적인 것 중 하나인 Flame Graph가 SVG파일로 출력되었습니다. Flame Graph에서 하단은 C의 영역이고 그 위쪽이 Node의 영역이 표시되는데 단일 SVG 파일이었지만 각 영역에 마우스를 이동할 때마다 인터렉티브하게 상세정보가 나오기 때문에 상당히 유용하게 디버깅할 수 있었습니다.


So, you've got a memory leak - Danny Coates
Danny는 메모리 누수에 대한 얘기를 했는데 메모리 누수는 찾기가 어렵다는 얘기를 했습니다. 메모리 누수가 있는 어플리케이션에서 보통 측정을 하면 메모리가 지속적으로 증가하는데 가장 일반적인 해결책을 정기적으로 재시작 해주는 것이라고 했습니다.(ㅋㅋㅋ)  V8은 힙메모리의 스냅샷을 그래프로 보여주는 기능이 있는데 스냅샷을 찍어도 다 비슷해 보여서 어떤 것을 봐야하는지 모르게 생겼습니다. 힙 메모리에 구조에 대해서 설명을 해주었는데 잘 받아적지를 못했네요 ㅋ 그래서 node에서 사용할 수 있는 v8-profiler를 만들었습니다.

현재 누수를 확인할 수 있는 도구들은 node-inspector가 있는데 더이상 사용하지 말고 webkit-devtools-agent를 사용하기를 권했습니다. node-inspecor와 비슷하지만 더 낫고 스냅샷을 떠서 비교해 볼 수 있기 때문에 꽤 유용합니다. node-inspector는 정말 유용하기 때문에 다른 사람이 이런 말을 했으면 무슨 소리냐고 무시했겠지만 Danny가 node-inspector를 만든 사람입니다.


메모리 누수를 찾으려면 스냅샷하나로는 찾을 수가 없고 여러개의 스냅샷을 찍어야 합니다. 그래서 먼저 베이스라인을 정한 뒤 가능한한 많은 스냅샷을 찍어서 객체들을 추적하면 누수를 찾을 수 있습니다. 스냅샷들을 사진에 비유해서 수천명이 있는 사진에서는 원하는 사람을 찾을 수 없지만 수천장을 찍어서 동영상처럼 만들면 누가 움직이는지 쉽게 찾을 수 있다고 했습니다.

이어서 데모를 보여주었는데 요청이 들어올 때마다 Foo 객체를 새로 만들어서 메모리 누수가 발생하는 예제였습니다. socat READLINE /tmp/node-repl 명령어를 실행한 다음 t.start()와 t.stop()을 실행하니까 자동으로 스냅샷이 찍혀서 출력되었습니다. 여러장의 스냅샷을 찍은뒤 t.list(index) 명령어로 해당 스냅샷을 볼수 있는데 이렇게 검사를 하니까 Foo 객체의 수가 늘어가는 것을 볼 수 있었습니다.(이건 직접 해봐야 감이 좀 올것 같더군요.)


node-memwatch - Jed Parsons
Jed는 Mozilla에서 일하는데 이번에 Node 애플리케이션에서 메모리 누수를 찾는 걸 도와주는 도구인 node-memwatch를 만들었고 이 세션은 node-memwatch를 발표하는 자리였습니다. 메모리 누수는 클로저나 이벤트 리스너의 사용에 따른 코드에서 발생할 수도 있고 때로는 직접 짠 코드가 아닌 곳에서 발생할 수도 있습니다. 사용하던 어플리케이션에서 누수가 발생해서 이틀을 고생한 끝에 요청이 완료될 때 모든 리스너를 제거해서 겨우 해결했습니다.

지금도 메모리 누수를 찾기위한 많은 도구들이 존재하고 있습니다. 하지만 node-memwatch를 만든 이유는 메모리 누수가 있을 때마다 알려주는 플랫폼 독립적인 디버깅 라이브러리가 필요했습니다. 그래서 node-memwatch의 API는 메모리누수에 대한 이미터, gc에 대한 이미터, heap diff 클래스, gc를 실행하는 함수를 제공합니다. 메모리사용량을 추적하기 위해서는 인터벌을 두고 process.memoryUsage()를 실행하거나 GC에서 V8:AddGCEpilogueCallback을 사용할 수 있습니다.


바로 실제로 동작하는 예제로 데모를 보여주었습니다. 첫 데모는 잘 동작하는 프로그램의 메모리로 node-memwatch에서 GC를 실행해서 최저 메모리가 고정되어 있었지만 메모리 누수가 있는 프로그램을 사용한 두번째 예제에서는 GC를 사용할 때마가 베이스라인이 점점 증가하는 것이 그래프로 나타났습니다. 그리고 node-memwatch는 누수에 대한 알림을 제공합니다. 누수라고 판단하는 기준은 연속적인 4번의 GC동안 Heap 할당이 계속 커지만 leak 이벤트를 발생시킵니다. 누수가 발생했음을 알았으므로 HeapDiff 클래스를 통해서 Heap 스냅샷으로 비교를 할 수 있습니다.  앞으로는 누수된 객체의 인스턴스의 예제를 보여주는 기능을 추가하려고 하고 있습니다.

node-memwatch는 완성도도 괜찮아보였고 누수를 찾기에 잘 구성되어 있는것 같아서 꽤 인상적이었습니다. 이건 바로 사용해 볼 수 있을 듯 합니다.
2012/07/28 01:39 2012/07/28 01:39