SNS에서 펼쳐지는 뜨거운 Node.js 토론에 합류하며

Node.js Logo

Node.js의 장/단점을 내세우며 Node.js는 차세대 서버용 프로그래밍 언어인가, 뛰어난 마케팅에 속아 넘어간 개발자들의 우상인가에 대해 의견이 분분한 것 같다.

Node.js를 반대하는 파(?)의 의견과 그 것에 따른 내 의견을 다음과 같이 정리하여 보았다.

  1. Event loop, 단일 Thread, 비동기 I/O는 혁신적인 기술이 아니고 이미 용도에 맞게 잘 쓰이고 있는 기술이다.

    나의 의견:

    나는 보통 Node.js를 네트워크 프로그래밍계의 Matlab이라고 종종 소개하곤 하는데. 왜냐하면,

    1. Event-loop, 비동기 I/O 등을 (프론트 앤드 웹 개발자 관점에서) 투명하게1 지원한다.
    2. 기존의 웹 개발자들이 큰 허들 없이 접근할 수 있다. (과학/수학자들이 Matlab을 큰 허들 없이 접근할 수 있듯이)

    Node.js의 가장 큰 메리트는 언어의 런타임 자체에서 동시성을 투명하게 해결하려는 데 있는 것 같다. 따라서, 기존의 반대파들이 말하는 ‘기존에 있는 기술, 새로울 게 없다’ 같은 의견은 사실 받아들이기 힘들다.

  2. 직렬 루틴에서 CPS(Continuation-passing style)을 강제한다.

    나의 의견:

    사실 Node.js에서는 개발의 생산성이나 코드의 가독성 등을 생각하기 시작하면 복잡해진다. CPS를 강제하여 코드를 구성할 경우, 다음과 같은 문제가 생긴다.

    1. 직렬로 구성할 수 있는 코드를 CPS로 구성하는 것은 기존 개발자에게 엄청난 혼란을 가져다 준다.
    2. 가독성과 생산성에 큰 지장을 주는 늘어지는 Callback.

    첫번째부터 짚고 넘어가 보자. 사실 직렬 코드를 일부로 비동기로 구성하는 것은 Node.js 숙련자가 아니라면 매우 힘들다(쉬울 것 같지만 막상 해보면 수많은 문제들이 속출한다). 그래서 이를 해결하기 위해 Control-flow 보조 라이브러리가 매우 많이 배포되고 있다. (참조: step, seq)

    두번째는 가독성과 생산성에 대한 이야기이다. 코드 블록을 구성하는데에 { ... } 를 사용하는 JavaScript는 Node.js의 Callback을 강요하는 컨벤션때문에 코드에 많은 혼란이 있을 수 있다. 이 또한 간결성과 가독성을 높히기 위해서 여러가지 대안들이 나왔다. (참조: CoffeeScript)

    사실 Node.js가 이런 문제에 대해서 고려가 안되었다기 보다는, 어쩔 수 없는 선택이였을 것이다. JavaScript의 주 사용자인 웹 개발자들은 기존 AJAX에서 관행적으로 기술되던 노테이션이 그대로 Node.js에서도 사용하니 큰 허들 없이 Node.js로 로직을 구성할 수 있다.

    그리고 이런 스타일이 익숙치 않은 개발자들이라도 이러한 문제들을 자바스크립트 특유의 생태계(Eco-system)로서 극복하고자하는 움직임을 알고 있는 사람이라면, 크게 신경쓰지 않을 것이다.

  3. Co-routine이 없다.

    나의 의견:

    파이썬은 2.5 버전 이전엔 Co-routine이 없었다. 많이 쓰이는 greenlet도 언어 자체의 기능은 아니다. 파이썬은 이러한 문제를 생태계가 해결해갔다. 이를 비추어 보았을 때, 언어 자체의 한계를 주장하며 Node.js를 비판하는 의견도 사실 이해가 쉽게 되지 않는다. 또한 Node.js용 Fiber/co-routine 구현 라이브러리는 이미 나와있다.(참조: node-fibers)

    언어의 확장 가능성으로 문법으로서의 확장써드파티로서의 확장. 이 둘을 놓고 보면, 다 장/단점이 있다. 사실 파이썬도 써드파티로서의 확장을 대부분 선택해온 언어 같고, 그에 비추어보면 Node.js의 행보는 아직 바람직하다. node-fibers를 활용한 응용은 아직 나와있는 것 같지 않으나, 앞으로 충분히 발전 가능성이 있다. 결론만 말하자면, 적어도 이제는 Co-routine이 없지는 않다.

사실 내가 Node.js를 비판한다고 한다면, JavaScript 자체에 대한 비판을 하겠다. JavaScript는 현재까지의 구현들이 고성능 처리나 서버를 위한 언어가 아니였고, 가벼운 웹 스크립트 언어였다. 서버용으로 사용하기에는 그에 따른 개발자들의 일반적인 컨센서스가 미성숙하다.

현재 많은 라이브러리들이 이런 미성숙함 속에서 개발되어지고 있고(혹은 개발되었고), 이는 커머셜 서비스에서 어떤 일을 초래할지 모르는 일이다.

아직은 미성숙한 언어 Node.js이다. 하지만 반짝 반짝 빛나는 점이 있는 만큼, 기존 개발자들의 시니컬한 태도나 시선보다는 새로운 언어에 대한 관심과 사랑이 필요할 것이다.


  1. 투명하다는 표현은 사실 매우 망설여지긴 한다. 처음에 IRC에서 사람들과 대화할 때는, 논란의 여지가 있음을 명시하고 대화를 했었는데, 혹시나 해서 추가해 본다. 자바스크립트 헤비 유져인 프론트 앤드 웹 개발자 입장에서는 CPS가 자연스러운 로직 구성이다. 나는 Node.js 내부 런타임이 수행해주는 여러가지 기능들을 봤을 때, 나름 투명하게 지원해주고 있다고 생각한다. 

This was posted 1 year ago. It has 7 notes and 0 comments.

Node.js의 메모리 관리

Node.js Logo

나는 평범한 Node.js 개발자이다. 회사에서 Node.js로 개발하면서 버그들을 발견하면 종종 패치하고 있는데, 글 재주는 없지만 Node.js를 열심히 해킹하면서 알아낸 몇 가지들을 두서없이 정리해보고자 한다.

그 시작으로 메모리 관리에 관해서 이야기 해보고 싶다.

서론

Node.js를 쓰는 개발자들이 메모리 관리라는 단어를 보면 상당히 어색할 것이라 추측한다. 왜냐하면,

  • 다른 언어처럼 할당에 관해서 낮은 수준(Low-level)의 제어를 할 수 있는 것이 아니다.
  • Garbage collector가 붙어 있으니 크게 신경 쓸 필요가 없다고 생각하기 때문이다.
  • 태생적인 이유(웹에서 쓰는 스크립트 언어)로 일반적인 컨센서스가 메모리 관리를 중요하게 생각하지 않는다.

하지만 Node.js로 프로그램을 작성할 때는 메모리 관리 측면에서 다음과 같은 필수 고려 사항이 있다.

  • Event loop를 고려한 코드를 짜야한다.
  • Garbage collection1을 고려한 코드를 짜야한다.

그렇지 않으면 Node.js는 Write-intensive한 프로그램이나 Compute-intensive한 프로그램에게는 독이 될 수 있다.

사실 Node.js core team도 이러한 사실을 인지하고 있으며, 버그다! 버그가 아니다! 의견이 아직도 분분한 것 같다. 내 솔직한 생각으로는, 사용자가 유도하지 않은 동작을 내포하고 있고, libuv와 node의 코어를 이해하며 코딩해야 한다는 것은 상식적으로 와닿지 않는다.

본론

Node.js(정확히 말하면 V8에서)는 mark-and-sweep 형식의 Generational GC2기 때문에 GC를 호출하는 시기만 적절하다면, 아주 Smart하게 메모리 관리가 이루어 질 수 있다. 그럼에도 불구하고 일어날 수 있는 문제점을 정리해본다.

일단 결론만 말하면, Event loop와 Garbage collection을 고려한 코드를 짜야한다.

사실 메모리 관리 얘기하는데 왜 갑자기 쌩뚱맞게 Event loop냐 라고 물을 수 있다. 아직 서두르지 마시라. Node.js는 5초에 한번씩 libuv의 타이머를 이용하여 Garbage collection을 수행한다(현재 버전 0.6.10).

// src/node.cc:155
static void StartGCTimer () {
    if (!uv_is_active((uv_handle_t*) &gc_timer)) {
        uv_timer_start(&gc_timer, node::CheckStatus, 5000, 5000);
    }
}

사실 이것만 보면 큰 문제는 없어 보인다. 하지만 서론의 고려 사항을 간과하지 말라! 5초에 한번 시작되는 GC는 사실 다음 Tick에 실행된다. uv_timer는 내부적으로 ev_timer를 사용하고 있는데, ev 문서를 참조하면 다음과 같다.

ev_timer - relative and optionally repeating timeouts

Timer watchers are simple relative timers that *generate an event after a given time*, 
and optionally repeating in regular intervals after that.

실행(Execute)이 아닌 이벤트(Event)를 생성(Generate)하고 있다. Event-loop의 특성 상(Event-loop에 관해서는 이 글에서 설명하지 않겠다) 당연한 기대 행동(Behavior)이기도 하다. 그래서 결국 다음과 같은 결론을 얻을 수 있다.

현재 Tick이 끝나지 않으면, GC는 수행되지 않는다!

다음 코드로 이 문제를 Reproduce해보자.

// test.js
while(true)
    console.log('Test');

를 작성하고,

$ node ./test.js

실행한다.

$ top

그리고 메모리 사용률을 체크해보기 바란다.

node ./test.js        98.8  00:22.73 320M+

코드 2줄짜리 프로그램인데도 22초만에 320M를 먹고있는 node 프로세스를 볼 수 있다. 계속 증가한다.

결론

그래서 나는 이를 해결하기 위한 2가지 대안을 제시한다.

방법 1. 반복적인 작업에 대해서는 process.nextTick을 활용한다.

function loop(){
        console.log('Test');
        process.nextTick(loop);
}
loop();

방법 2. node의 --expose-gc 옵션을 켜서, 직접 GC를 호출한다.

// $ node --expose-gc test.js
while(true){
    console.log('Test');
    gc(true);
}

회사에서 Node.js로 짜여진 서버 프로그램으로 서비스를 운영 중인데, 커머셜 서비스의 경우 위의 내용들을 간과한 프로그램은 큰 코 다치기 십상이다. 참고로 나는 운영중인 가상화 시스템들이 다운되는 현상으로 고생하고 있었는데, KVM 소스를 뜯어보기도하고 별 삽질을 다 하다가 최근에 원인을 찾은게, Node.js의 메모리 할당 폭주였다 (또 웃긴게, 현재 KVM은 메모리가 가득차면 뻗어버리는 버그가 있다. 맞물려서 합작으로 날 물먹인거였다…). 그래서 이제 메모리 할당 폭주를 해결하려고 Node.js 코드 뜯어보고 libuv 뜯어보고…. 수많은 강을 건너왔구나.


다음 편은 이번 내용과 맞물려서 ‘Node.js의 퍼포먼스 관리’에 대해서 써볼까 한다.

예고) 많은 Node.js용 라이브러리나 프로그램들이 이를 간과해서 짜여져 있다. 간단한 예제로서 5배 가까이 성능 향상이 되는 것을 증명해볼 수 있다(…)


  1. Garbage collection: 불필요한 메모리 공간을 자동으로 해제시켜 주는 기능. 해당 기능을 가지는 프로그램을 Garbage collector라고 한다. 

  2. Generational GC: Young과 Tenured를 분리(Generation으로 분리)하여 관리하는 Garbage collection. 새로 생긴(Short-term) 공간과 오래 쓰고 있는 공간(Long-term)의 Garbage collection 알고리즘을 다르게 하여 효율적인 Garbage collection을 수행할 수 있다. 

This was posted 1 year ago. It has 11 notes and 0 comments.

블로그를 개설했다.

블로그를 개설했다. 전에도 몇가지 블로그 서비스를 이용했었는데 정착하지 못하고, 이거 저거 쓰다가 텀블러에 도착.

홍민희1형의 추천으로 개설해서 안쓰고 방치하다가 다시 쓰게 됬다. 다시 쓰게 된 이유는 다음과 같다.

  • Markdown extra2를 지원
  • Facebook/twitter와 같은 Social-networking service와 연동 가능
  • Custom domain 지원

최근 Markdown의 간결하고 강력하고 편리한 표현에 매료되어, 사내 문서 작성에도 애용하고 있다. 그러다보니 개인적인 작업들도 Markdown으로 작성하고 싶어져서 찾아보니, 텀블러가 적절하다는 판단을 내렸다. 텀블러를 쓰면 기존의 SNS 서비스와도 연동할 수도 있어서 딱히 부담도 없다. 덤으로 블로그와 기존 개인 서버들에 도메인을 씌우려고 도메인3도 샀다.

앞으로 개인적인 작업들을 잘 정리해서 문서화 해봐야겠다.


  1. 존경하는 고등학교 선배다. Software maestro 멘토로 활동하시며, 벤처 기업인 Styleshare의 CTO를 맡고 계신 개발자. 

  2. Markdown의 확장 스펙. 가벼운 마크업 언어중의 하나다. 

  3. nerds.kr, 기간 5년으로 구매했다. 참고로 이 블로그의 주소는 blog.nerds.kr이다. 

This was posted 1 year ago. It has 0 notes and 0 comments.