본문 바로가기
Experience/네이버 부스트캠프

[네이버 부스트캠프] node.js를 한번 파보자

by krokerdile 2024. 8. 20.

멤버십을 시작하게 되면서 자바스크립트로 개발을 하는 과정에 Node를 많이 다루게 되서 한번 정리해보면 어떨까 해서 멤버십을 하면서 배우고 접하는 내용을 정리하는 김에 글로 남겨 보려고 합니다. 특히 오늘 호눅스 님의 강의를 들으면서 이거 정리 안해놓으면 또 까먹겠다 싶기도 해서 강의 해주신 내용을 포함해서 이것저것 정리를 해봤습니다.일단 한번 쭉 적어보는게 목표고 참고할 수 있는 내용들도 함께 적어보려고 합니다. 

Node.js란?

node를 구글에 검색했을 때 나오는 노드 공식 홈페이지

Node.js를 구글에 검색해보면 처음으로 나오는 링크가 바로 Node.js 공식 홈페이지 입니다.

여기서 아래 부제를 읽어보면 다음과 같습니다. "Node.js는 chrome v8 javascript 엔진으로 빌드된 JavaScript 런타임입니다". 

위의 부제에서 유추할 수 있듯이 Node.js는 자바스크립트를 어디에서나 사용할 수 있도록 환경을 제공해주는 녀석이라고 볼 수 있습니다. 런타임은 특정 언어로 만든 프로그램, 언어 등을 실행할 수 있는 환경이고 javascript는 html의 조작과 변경을 위해 사용하는 언어 입니다. 정적 언어인 html에 동적인 요소를 추가해서 다이나믹 하게 웹페이지를 바꿔주는 게 javascript의 역할 이라면 javascript를 해석하고 사용해줄 수 있게 해주는 게 바로 브라우저와 Node.js라고 저는 이해를 했습니다. 

Node.js는 브라우저에서 한 발 더나가서 어디에서나, 브라우저 밖에서도 동작을 할 수 있도록 해주는 개발환경이라고 생각을 하면 될 것 같습니다. 

V8엔진은 뭘까?

이전에 여러 javascript 엔진이 사용되고 있었지만 다른 javascript 엔진은 웹 특성상 유저와 상호작용을 위해 즉시성이 있는 인터프리터 방식을 사용하는데, 이는 코드가 많아질수록 속도가 느려진다는 단점이 있었다고 합니다. 그래서 구글이 크롬에서의 성능 향상을 위해서 만들었다고 합니다. 원래 있는 녀석들을 그냥 사용하면 안되냐고 할 수도 있지만 브라우저에 대한 최적화를 통해서 성능을 올려 빠르게 실행을 할 수 있도록 구글이 V8엔진을 만들게 됩니다. 

javascript를 만드신 분이 브렌던 아이크 라는 분이신데 이분이 넷스케이프에 있을 때 javascript를 10일 만에 만들었다고 합니다(유닉스 만든 사람은 3일 만에 만들었다는데 도대체 🫠). 그래서 넷스케이프도 자연스럽게 javascript를 사용했을 거고 거기에도 스파이더 몽키라는 javascript 엔진이 있었다고 합니다. 하지만 이 친구도 인터프리터만 있었기 때문에 속도가 느렸고 범용적인 언어로 사용을 하기가 어려웠다고 합니다.

이런 속도와 성능 문제를 해결 해주기 위해서 V8엔진을 이제 구글이 만들었다고 생각이 듭니다. V8엔진 같은 경우에는 인터프리터와 JIT를 함께 사용을 해서 최적화를 했다고 합니다. V8 엔진의 주요 특징 중 하나는 바이트 코드를 사용한다는 점입니다. 이는 여러 가지 장점을 가져다줍니다. 바이트 코드를 실행하기 위해서는 두 가지 방식이 사용됩니다.

먼저, javascript 코드는 인터프리터가 읽어서 실행합니다. 하지만 V8은 여기서 그치지 않고 한 단계 더 나아갑니다. 자주 실행되는 부분에 대해서는 JIT(Just-In-Time) 컴파일러가 직접 읽어서 실행합니다. 이 JIT 컴파일러는 실행 시점에 코드를 기계어로 변환하여 처리합니다.

이렇게 바이트 코드와 JIT 컴파일러를 함께 사용함으로써 V8 엔진은 실행 속도를 대폭 향상시켰습니다. 이는 기존의 인터프리터 방식만 사용하던 엔진들과 비교했을 때 큰 성능 차이를 만들어냈죠.

V8 엔진의 이런 혁신적인 성능 향상은 javascript의 활용 범위를 크게 넓혔습니다. 특히 라이언 달이라는 개발자가 V8 엔진의 속도에 주목했습니다. 그는 이제 javascript를 브라우저 밖에서도 범용적으로 사용할 수 있겠다고 생각했고, 이것이 Node.js의 탄생으로 이어졌습니다.

Node.js는 V8 엔진을 기반으로 만들어진 javascript 런타임 환경입니다. Node.js의 가장 큰 특징은 비동기 논블로킹 I/O입니다. 이전의 서버들이 주로 동기 방식으로 작동하며 I/O 작업 시 블로킹되는 것과 달리, Node.js는 비동기적으로 작동하여 I/O 작업 중에도 다른 작업을 처리할 수 있습니다. 이는 서버의 효율성과 성능을 크게 향상시켰고, 자바스크립트가 서버 사이드 프로그래밍에서도 널리 사용되는 계기가 되었습니다.

이렇게 V8 엔진의 등장은 자바스크립트의 실행 속도를 획기적으로 개선했을 뿐만 아니라, 자바스크립트의 활용 범위를 크게 확장시켰습니다. 웹 브라우저에서의 사용을 넘어 서버 사이드 프로그래밍, 데스크톱 애플리케이션 개발 등 다양한 분야에서 자바스크립트가 사용되는 현재의 모습은 V8 엔진이 있었기에 가능했다고 볼 수 있습니다.

Node.js를 사용하는 이유는?

Node.js를 사용하는 이유는 다양하겠지만 대부분 나오는 내용 중에 일단 가장 중요한 요소는 "비동기 논블로킹 I/O" 라는 특징이 가장 크게 작용하는 것 같습니다. 그 외에 이유들도 한번 정리해보겠습니다.

  • 브라우저 밖의 환경에서도 자바스크립트를 사용하기 위해서
  • 자바스크립트 관련 생태계가 잘 구성 되어져 있다.
  • 자바스크립트 만으로 프론트엔드와 백엔드 모두를 개발하기 위해서 
  • npm이라는 기본 패키지 관리자가 포함되어져 있어 npm을 통해 수십만 개의 오픈 소스 라이브러리를 쉽게 설치 하고 관리할 수 있기 때문에 

기본적으로는 위와 같은 이유들로 인해서 많이 사용을 하는 것 같습니다. 이러한 이유들 중 일부는 node.js의 특징 덕분이라고 생각을 합니다. 주요한 Node.js의 특징은 정리해보면 다음 정도로 정리를 할 수 있을 것 같습니다. 

  • 비동기 논블로킹 I/O >>> 이벤트 루프 기반으로
  • 싱글 스레드
  • 고성능 네트워크 서버
  • 방대한 모듈 제공(npm)
  • v8 엔진을 사용한다

Node.js는 싱글 스레드 일까? 멀티 스레드 일까?

저도 인턴 지원을 했었을 때 받았던 질문 중 하나이고, Node.js를 공부하면서 가장 자주나오는 이야기 중 하나 인 것 같습니다. 인턴 면접 중에 받았던 질문에 대해서 "브라우저를 따라서 만들다 보니 Node.js도 자연스럽게 싱글스레드가 된 부분도 있다"라는 얘기를 해주신 면접관 분도 계셔서 참고하시면 좋을 것 같습니다. 

기본적으로는 Node.js를 싱글스레드 라고 지칭 하는 이유는 이벤트 루프와 콜스택이 결국 하나로 유지 되어지기 때문이라고 생각을 합니다. 멀티 쓰레딩을 worker_thread 등을 활용해서 할 수 있지만 실질적인 핵심요소인 이벤트 루프와 콜스택은 하나로 유지 되어지기 때문에 이론적으로 싱글 스레드라고 할 수 있다고 생각을 했습니다. 

다른 언어와 비교를 한번 해본다면 자바 같은 경우에는 스레드별로 콜스택과 같은 요소들을 만들어줄 수 있기 때문에 싱글 스레드가 아닌 멀티스레드라고 볼 수 있는 것입니다.

비동기 vs 멀티 쓰레드

웹 서버의 발전 과정을 살펴보면, 과거에는 아파치 서버와 스프링의 조합이 주로 사용되었습니다. 하지만 최근에는 nginx가 아파치를 대체하는 추세입니다. 이러한 변화의 배경에는 서버 처리 방식의 차이가 있습니다.

nginx는 이벤트 중심의 접근 방식을 채택하여 하나의 스레드로 여러 요청을 처리할 수 있습니다. 반면 아파치는 프로세스 기반 접근 방식으로, 매 요청마다 새로운 스레드를 생성하고 할당합니다. nginx의 등장 배경에는 C10k 문제(동시에 10,000개의 연결을 처리하는 문제)를 해결하려는 노력이 있었습니다.

nginx의 가장 큰 장점은 비동기 처리가 가능하다는 점입니다. 반면 아파치나 스프링 같은 전통적인 서버들은 동기적으로 처리하며, 멀티 프로세스 방식이나 멀티 스레드 방식을 사용합니다. 이는 서버에 큰 부하를 주고 메모리 리소스를 많이 소모하게 되어 오버헤드 문제와 동시성 문제를 야기할 수 있습니다.

스프링 부트를 사용하면 톰캣이 내장 서버로 들어가는데, 톰캣은 동기의 블로킹 방식을 사용합니다. 이는 I/O 작업이 끝날 때까지 스레드가 대기 상태를 유지하므로 메모리를 많이 사용하게 됩니다. 이것이 스프링 서버가 많은 메모리를 필요로 하는 이유 중 하나입니다. 한편, 스프링의 비동기 방식에서는 Netty나 Jetty와 같은 서버를 사용할 수 있습니다.

반면에 Node.js는 싱글 스레드 모델을 사용합니다. 이는 메모리 사용량을 줄일 수 있는 장점이 있습니다. 예를 들어, 1000명의 사용자가 동시에 접속해도 이벤트 루프에 의해 비동기적으로 처리되기 때문에 메모리 효율성이 높습니다.

그렇다면 I/O 작업의 속도는 어떨까요? 실제로 I/O 작업(디스크 - 네트워크 입출력)의 속도는 서버 모델과 관계없이 거의 동일합니다. 이는 실질적인 I/O 처리가 운영체제의 커널에 의해 이루어지기 때문입니다. 커널의 작업은 결국 랜카드나 디스크와 같은 물리적 하드웨어가 처리합니다. 버퍼와 같은 보조 장치가 있긴 하지만, 대부분의 경우 실제 작업은 하드웨어 단독으로 수행됩니다.

디스크 I/O의 경우, 병렬 처리 능력이 제한적입니다. 많아야 2개의 작업을 동시에 처리할 수 있을 뿐입니다. 이는 하드웨어의 물리적 한계 때문입니다.

결론적으로, 비동기 모델과 멀티 스레드 모델은 각각의 장단점이 있습니다. 비동기 모델은 메모리 효율성이 높고 동시성 처리에 강점이 있지만, 실제 I/O 작업의 속도 향상에는 한계가 있습니다. 반면 멀티 스레드 모델은 직관적인 프로그래밍이 가능하지만 리소스 사용량이 높습니다. 따라서 애플리케이션의 특성과 요구사항에 따라 적절한 모델을 선택하는 것이 중요합니다.

단일 시간당 처리량 측면에서 비동기 모델과 멀티 스레드 모델을 비교해보면, 비동기 모델이 더 높은 성능을 보이는 경향이 있습니다. 이는 주로 오버헤드 때문입니다.

멀티 스레드 모델에서는 각 요청마다 새로운 스레드를 생성하고 관리해야 합니다. 이 과정에서 발생하는 컨텍스트 스위칭(context switching)과 스레드 생성/소멸에 따른 오버헤드가 상당합니다. 특히 동시 접속자 수가 증가할수록 이러한 오버헤드는 더욱 커지게 됩니다.

반면, 비동기 모델은 단일 스레드에서 이벤트 루프를 통해 여러 요청을 처리합니다. 스레드 생성이나 컨텍스트 스위칭에 따른 오버헤드가 없기 때문에, 동일한 하드웨어 리소스로 더 많은 요청을 처리할 수 있습니다.

 

고전 돌아보기, C10K 문제 (C10K Problem) | 올리브영 테크블로그

오래된 과거에서 시작하는 Node.js의 비동기 처리로의 여정

oliveyoung.tech

 

 

nginx와 apache의 차이 (C10K 문제)

https://www.youtube.com/watch?v=6FAwAXXj5N0&t=12s 1995년. 아파치 서버가 개발되었다. 아파치 서버는 요청이 들어오면 커넥션을 생성하기 위해서 프로세스를 만든다. -> 유닉스 계열 OS가 네트워크 커넥션을

blog.hojaelee.com

이러한 특성 때문에 C10k 문제(동시에 10,000개의 연결을 처리하는 문제)를 해결하기 위해 Node.js와 같은 비동기 모델 기반의 플랫폼이 등장하게 되었습니다. Node.js는 높은 동시성을 효율적으로 처리할 수 있어, 특히 실시간 웹 애플리케이션이나 API 서버와 같이 많은 동시 연결을 다루는 시나리오에서 강점을 보입니다.

하지만 이것이 모든 상황에서 비동기 모델이 항상 우수하다는 의미는 아닙니다. CPU 집약적인 작업이나 복잡한 비즈니스 로직을 처리해야 하는 경우에는 멀티 스레드 모델이 더 적합할 수 있습니다. 또한, 개발의 복잡성 측면에서도 비동기 프로그래밍이 더 어려울 수 있습니다.

결론적으로, 단일 시간당 처리량과 높은 동시성 처리가 중요한 경우 비동기 모델이 유리하지만, 애플리케이션의 특성과 개발 팀의 역량 등을 종합적으로 고려하여 적절한 모델을 선택해야 합니다. 현대의 많은 시스템들은 이 두 가지 접근 방식을 혼합하여 사용함으로써 각 모델의 장점을 최대한 활용하고 있다고 합니다. 

간단하게 정리를 봤는데 추가적으로 알아야 될 요소들이 아직 많이 남아서 아래 리스트로 적어뒀습니다.

 

  • 이벤트 루프와 콜 스택의 구체적인 작동 방식
    • 이벤트 루프의 단계별 처리 과정 (타이머, I/O 콜백, poll, check 등)
    • 콜 스택, 태스크 큐, 마이크로태스크 큐의 관계
    • 비동기 작업의 우선순위 처리 방식
  • Node.js 외의 자바스크립트 런타임 환경
    • Deno: Ryan Dahl(Node.js 창시자)이 만든 새로운 런타임. TypeScript 지원, 보안 강화 등의 특징
    • Bun: 웹킷의 JavaScriptCore 엔진 기반, 빠른 속도와 올인원 툴킷 제공
    • 이들과 Node.js의 차이점 및 각각의 장단점 비교
  • Node.js의 내부 구조
    • libuv 라이브러리의 역할과 중요성
    • Node.js의 모듈 시스템 (CommonJS vs ES modules)
    • Node.js의 스트림과 버퍼 개념
  • Node.js의 성능 최적화
    • 클러스터 모듈을 이용한 멀티 프로세싱
    • worker_threads를 이용한 CPU 집약적 작업 처리
    • 메모리 누수 방지와 가비지 컬렉션 최적화 방법
  • Node.js의 보안
    • 주요 보안 위협과 대응 방안
    • 안전한 의존성 관리 방법 (npm audit, snyk 등 활용)
  • 실제 프로덕션 환경에서의 Node.js
    • 로깅, 모니터링, 에러 핸들링 전략
    • 배포 및 확장 전략 (Docker, Kubernetes 등과의 연계)