Node.js가 빠른 이유를 구글에 검색하면 수많은 결과가 나온다. 많은 글에서 싱글스레드 기반의 비동기 이벤트 루프를 사용하기 때문에 빠르다고 한다. 더 깊게 들어가면 싱글스레드인 메인 스택에서 로직을 처리하고 비동기 작업(특히 IO 관련 작업)을 처리하는 스레드 풀이 존재하기 때문에 비동기 작업을 멀티스레드로 처리한다는 점이다.
일반적으로 논블로킹과 블로킹 방식의 차이를 물어보면 음식점의 예를 많이 든다. 논블로킹은 주문 순서대로 처리하는게 아닌 빨리 처리되는 순으로 음식을 내오고 블로킹 방식은 주문 받은 순서대로 음식을 만들고 내오기 때문에 응답속도의 면에서 차이가 난다는 것을 강조한다.
이 그대로 블로킹과 논블로킹의 차이를 면접에서 대답했는데 바로 꼬리질문이 들어왔다. 스프링부트처럼 멀티스레드로 요청을 처리하면 응답속도면에서 노드와 차이가 없기 때문에 성능적으로 차이가 나지 않는게 아닌지?
라는 질문을 받았다. 단순히 그렇다는 사실만 알고 깊게 생각하지 않았기 때문에 외면했을뿐 사실 응답속도 측면에서는 멀티스레드로 받으면 차이가 나는지 않는 것을 알고 있었다. 결국 대답을 하지 못했고 그렇게 알고 있다고만 대답하고 넘어갔다.
다행히 1차 기술면접은 통과했지만 의문이 생겨 이를 찾아 정리한다.
노드가 싱글스레드 기반으로 처리된다고 하여 처음 배우는 사람들이 오해하는 것이 정말로 스레드 하나만 사용되는거고 비동기처리는 특수하게 처리되는 것이라는 것이다(저만 그랬을 수도 있구요... 아니라면 ㅈㅅ)
사실 비동기 처리는 멀티 스레드로 이루어지는 작업이다. 아니 근데 노드는 싱글 스레드라면서 왜 멀티 스레드죠? 하고 반문할 수 있다. 그것은 이벤트루프에서 콜스택이라 부르는 메인 처리 스택이 한 개이기 때문에 싱글스레드라는 것이다. 다만 비동기 작업을 수행해야 할 때는 이를 담당하는 스레드 풀에 작업을 위임하고 싱글스레드는 계속 돌아가다 위임한 비동기 작업이 완료되면 다시 메인 스택에서 작업을 수행하기에 싱글 스레드라고 하는 것이다.
마찬가지로 스프링이 다중 요청을 받으면 미리 생성해둔 스레드풀에서 스레드를 꺼내 할당해 작업을 수행한다. 즉 요청 할당은 멀티스레드를 활용해 비동기적으로 작동하는 것이기 때문에 들어온 순서대로 요청이 처리되지 않는다. 따라서 응답속도에 관한 위의 대답은 틀린 것이 된다.
요청을 받는 부분은 서로 비동기적으로 작동하기 때문에 동일하다면 어디서 성능 차이가 발생하는 것일까. 차이점은 노드는 IO 작업에 완전한 비동기 처리를 지원한다는 것이다.
요청을 비동기로 받는 것까진 동일하지만 Node는 하나의 요청 처리 과정 내에서 필요하다면 비동기로 작업을 위임하고 기다리지 않고 다른 작업을 수행한다. 따라서 기다리는 시간이 없거나 매우 적다!
하지만 스프링은 IO 작업과 같이 시간과 리소스를 필요로 하는 작업을 한다면 요청 처리를 위해 할당받은 스레드가 대기하게 된다. 요청받는 것은 비동기로 받지만 언어의 특성상 요청의 처리는 블로킹 방식으로 진행되는 것이다. 또 요청이 많아질수록 스레드의 개수가 많아질 것이고 이로 인한 컨텍스트 스위칭으로 인해 오버헤드가 발생하게 된다. 결국 스레드의 대기 시간과 컨텍스트 스위칭의 오버헤드가 성능 차이를 불러오게 된다
이를 그림으로 나타내면 아래와 같다
이러한 차이 때문에 성능적 차이가 나타나는 것이다
노드에서 async/await
키워드를 사용하면 프로그램의 흐름을 마치 블로킹 방식처럼 돌아가는 것처럼 느껴진다. 비동기 함수를 기다리기 때문에 블로킹 방식 아니야? 라고 생각할 수 있을 것이다. 하지만 async/await
은 Promise
를 사용하기 위한 문법적 설탕이라는 것을 기억해야 한다. async 함수는 실제적으로 프로미스를 반환하며 await 뒤의 코드는 프로미스의 then
블록이다. 따라서 async/await
은 논블로킹을 작동한다
노드의 싱글 스레드 방식이 효과적이려면 3가지를 만족해야한다
이는 현 웹서비스의 특징들이기 때문에 노드의 이점이 잘 드러나게 된다. 하지만 IO 작업이 주가 아닌 무거운 CPU 연산이 필요하다면 싱글 스레드의 단점이 드러나게 된다. 따라서 서비스의 특징에 따라서 선택해야 한다.
참고
Node.js가 Spring MVC보다 성능상 유리할까?
Spring MVC : 동시요청 멀티 쓰레드 간단한 개념
What Makes Node.js Faster Than Java?
음,,, 스프링과 노드가 공평하게 하나의 쓰레드만 사용해야 하는 상황이라면
그니까 하나의 쓰레드 당으로 비교하면 그럴 수 있지만, 요즘에는 멀티쓰레드에 멀티코어라 이렇게 비교하는 것은 잘못되지 않았을까요