최근에 Nest 를 공부하기 시작했는데, 뭔가 더 기본적인 개념을 알고 싶어서 먼저 Node.js 에 대해 정리를 해보려고 한다.
Node.js 란?
자바스크립 런타임 환경, 자바스크립트로 서버 환경을 개발할 수 있는 런타임이다. 비동기 이벤트 기반의 런타임이라고 하는데... 그래서 이벤트 루프가 주요한 개념으로 포함된다.
노드의 구성 요소로는 크게 V8 엔진과 libuv 라이브러리가 있다. V8 엔진은 구글 크롬의 브라우저 엔진과 동일한 것이고, 인터프리터를 기반으로 자바스크립트 코드를 기계어로 변환하는 역할을 함. 그리고 libuv 라이브러리는 비동기 I/O를 담당한다. 이벤트 루프 및 비동기식 태스크 처리, 파일시스템 접근 및 네트워크 등... 서버사이드 개발을 위해 필요한 동작을 libuv 라이브러리를 통해 지원받는다. 이 외에 http-parser, c-ares, OpenSSL, zlib 등의 라이브러리 등으로 구성된다고 함.
V8 엔진은 자바스크립트 + C++ 그리고 libuv 라이브러리는 C++ 로 작성되었다. 따라서 노드는 C++ 그리고 자바스크립트 기반으로 구성되었지만 우리가 자바스크립트 코드로 노드 서버를 프로그래밍 할 수 있도록 레이어 역할을 한다고 볼 수 있다.
프로세스 & 쓰레드 그리고 쓰레드 풀
먼저 용어 정리... 프로세스는 실행중인 프로그램 그 자체(인스턴스) 를 의미하고, 쓰레드란 프로세스 내에서 실행되는 실행의 흐름..
그리고 자바스크립트는 싱글 스레드 언어로 익히 알려져 있다. 싱글 스레드 기반에서 어떻게 동시성을 관리하느냐 하면, 브라우저와 마찬가지로 이벤트 루프를 기반으로 동작한다. 이벤트 루프가 동작하기 전, Node 의 싱글 스레드에선...
프로그램 초기화 -> 최상단 코드 실행 -> 모듈 가져오기 -> 이벤트 콜백 등록 과 같은 과정을 거친 뒤 이벤트 루프를 실행한다.
만약 싱글스레드 내의 이벤트 루프에서 실행되기에 너무 무거운 작업이라면 - 예를 들어 블록킹을 유발하는 I/O 작업이라던지... 그럴 땐 Node.js 가 libuv 라이브러리를 통해 제공하는 쓰레드 풀에 해당 작업의 처리를 맡길 수 있다. 쓰레드 풀은 기본 4개의 쓰레드를 가지고, 최대 128개 까지의 쓰레드로 쪼갤 수도 있다고 함. 그리고 이 쓰레드들은 메인 프로세스의 싱글 스레드와 구별된다.
싱글 스레드에서 쓰레드 풀로의 위임은, 개발자가 직접 핸들링 한다기 보단 Node.js 내부에서 이루어지며, 쓰레드 풀을 활용하는 API 를 사용하면 된다. 예를 들면 fs 모듈... 파일시스템, 네트워킹과 같은 I/O 작업이나 CPU 집약적인 작업을 할 때 내부적으로 이런 방식으로 동작한다고 이해하면 될 것 같다.
이벤트 루프
앞서 잠깐 노드는 비동기 이벤트 기반의 런타임이다 - 라는 문장을 썼는데, 그렇게 동작할 수 있도록 하는 핵심은 바로 이벤트 루프이다. 이벤트 기반 아키텍쳐에서는 서버가 발생하는 모든 사건을 이벤트로 간주하고 이에 대응을 하기 위해 콜백 함수를 실행한다. 그 사건이란 예를들면 HTTP 요청의 도착, 파일 읽기의 완료 등... 그러면 노드는 해당 이벤트에 대해 미리 등록된 콜백을 비동기적으로 처리하고, 이런 비동기 처리 덕분에 동시에 발생하는 여러 요청을 효과적으로 처리할 수 있다. 그리고 이런 효율적인 처리 덕분에 당근 서버의 성능과 자원을 최적화 할 수 있는 아키텍쳐를 가지고 있다.
암튼 그래서 이벤트 루프는 이벤트에 대응하는 콜백 함수를 실행시키는 역할을 담당하는데, 여기에도 각 단계? 순서가 있다... 그리고 각 phase 는 해당하는 별도의 콜백 큐를 가지고 있다. 각각의 콜백 큐에 이벤트에 해당하는 콜백을 쌓아놓고 실행하는 것이다...! 가장 주요한 phase 로는 크게 아래와 같다...
1. Timers : setTimeout 이나 setInterval 에 의해서 지정된 콜백을 실행하는 단계
2. I/O Callbacks : 대부분의 비동기 I/O 작업의 콜백을 이 단계에서 처리한다. (파일시스템, 네트워크 작업 등..)
3. I/O Polling : 새로운 I/O 이벤트를 폴링하고 이와 관련된 콜백을 실행한다. 그러니까 새로운 단계의 I/O 작업을 대기하거나 감지하는 역할을 한다고 함... 뭔가 둘의 차이가 뭔지 아직 잘 모르겠지만 일단 넘어간다.
4. Check : SetImmediate 함수에 대한 콜백이 이 단계에서 실행된다. Node.js 에서 제공되는 비동기 함수인데, 이벤트 루프의 한 사이클이 완료 되면 그 완료된 직후에 실행할 콜백 함수를 예약하는 역할을 한다고 함.
5. Close Callbacks : 일부 close 이벤트가 이 단계에서 실행!
그리고 각 phase 의 콜백 큐 이외에도 process.nextTick 큐와 resolved 된 프로미스들을 위한 마이크로 태스크 큐가 존재하는데, 만약 이 두 큐에 작업이 하나라도 있다면, 각 phase 의 실행 직후에 실행된다.
이벤트 루프에서 타이머를 기다리거나, I/O 를 기다리는 일이 없다면, 프로그램을 종료한다. I/O 를 기다린다는 것은 예를들면 HTTP 요청을 기다린다는 의미...
자바 vs Node
내가 알고 있는 백엔드 기술 두 가지... 사실 우리 회사 백엔드에서 대부분의 프로젝트를 Nest 로 작성했다가 최근들어 마이그레이션 까진 아니지만 암튼 자바 스프링을 사용한 프로젝트들이 좀 있어서 과연 두 기술간 차이는 뭘까 궁금해졌다.
간략히 설명하면 자바는 멀티스레드 환경에서 동기적으로 작업을 처리하고, 객체 지향 설계 원칙에 따라서 대규모 서비스를 안정적으로 구축하기에 적합한 언어라고 할 수 있겠다. 하지만 멀티 스레드이기 때문에 더 많은 자원을 소모한다는...
그리고 노드는, 앞서 설명한 바와 같이 싱글스레드 + 이벤트 기반의 설계로 동시성 문제를 해결하고 있어 조금 더 가볍다는 장점이 있다. 그리고 프론트엔드 개발자로서는 자바스크립트를 기반으로 하기 때문에 언어에 대한 러닝커브가 낮다는 점, 풀스택으로 개발하기엔 이만한 기술이 없다는 점...? 다만 싱글 스레드의 한계로 아주 무겁고 대규모 서비스를 만들기에는 부적합한 면도 있을 것이라 생각된다...!
앞으로 잘 해보자..~
'Stacks' 카테고리의 다른 글
[Electron.js] electron 의 rebuild 란? (0) | 2024.02.18 |
---|---|
[Node.js] Node의 require module 은 어떻게 동작하는가? (0) | 2024.02.04 |
[Node.js] Streams 에 대해서 알아봐요. (0) | 2024.01.28 |
[Electron.js] 일렉트론에 대해서 알아보자! (0) | 2024.01.21 |