洪民憙 (홍민희) 블로그

이하의 글은 2012년에 쓴 것입니다. 오래된 글인 만큼, 현재의 생각과 전혀 다른 내용도 많이 포함되어 있고, 당시와는 상황이 많이 달라진 점도 있습니다. 또한, 그 당시에 잘못 알려졌던 정보도 포함되어 있을 수 있습니다. 어찌됐든 저는 제 오래된 글이 회자되는 것을 저어합니다. 읽기에 앞서 양해를 부탁드립니다.

나도 node.js가 하잎에 비해서 형편 없다고 생각하지만, 이런 식의 반박은 제대로 된 이유가 되지 못한다고 본다. (누누히 얘기했지만 내가 생각하는 node.js의 단점은 직렬 루틴에서도 CPS를 강제한다는 점 뿐이다.)

node.js가 20년 전 GUI 프로그래밍에서 쓰이던 이벤트 루프와 다른 점은 복잡도를 얼마나 제어하느냐에 있다.

그 때는 렉시컬 스코핑(lexical scoping)과 람다(lambda)는 Lisp이나 Smalltalk 같은 언어에서야 들리던 용어였고 C/C++에서는 비동기 I/O 프로그래밍을 할 때 중지된 문맥을 복구하는 것을 직접 구현해야 했다. 이 부분은 렉시컬 스코핑이 되는 람다를 콜백으로 던지면 이전 문맥이 바깥쪽 스코프에 있으므로 문맥을 메모리 어딘가에 저장하는 것을 프로그래머가 직접 구현할 필요가 없어진다. (코루틴을 쓰면 복잡도는 더욱 내려간다.)

node.js의 동시성이란 I/O에 대해서만 이뤄진다. 연산보다는 I/O에 바운드되는 대부분의 네트워크 프로그래밍에 있어서는 굳이 쓰레드를 쓸 필요가 없다. 그리고 OS에서 제공하는 쓰레드는 대체로 더 비싸다.

효율적인 알고리즘을 사용한다면 처리속도와 메모리와 같은 저장소의 효율 상에는 항시 반비례 관계를 가지게 된다. 처리속도를 높이려면 더 많은 메모리를 사용하면 되고, 메모리를 적게 쓰려면 처리 속도를 희생하면 된다.

맞는 얘기 같지만 I/O에 대해서는 틀리다. I/O의 특징은 대부분의 시간이 아무 것도 하지 않고 대기를 한다는 점에 있다. (요청을 했는데 서버에서 응답을 1분 뒤에야 주는 상황, 표준 입력에서 읽으려고 하는데 사용자 입력이 없어서 한없이 기다려야 되는 상황 등을 생각해보자.) 당연히 연산과 메모리는 대체로 교환 가능한 지표이지만, 다른 것을 할 수 있는데 I/O 응답이 없다고 기다리는 것은 그냥 낭비다. 교환 없이 성능을 올릴 수 있다.

마지막으로 꼭 node.js 뿐만이 아니라 현대적인 네트워크 프로그래밍 플랫폼이면 다들 갖추고 있는 부분이지만, epoll이나 kqueue, IOCP 등의 API를 직접 쓰는 대신 플랫폼 중립적인 이벤트 API를 다룰 수 있다는 점도 20년 전과는 매우 큰 차이라고 볼 수 있다.

이렇게 node.js 실드쳐주는 것으로 훈훈한 마무리를 할 수도 있겠지만, 그래도 역시 node.js는 그 CPS 강제하는 것은 코루틴 도입해서 어떻게 좀 없애놔야 한다고 본다.