백엔드/Node
Node.js 가 뭐임?
ZestLee
2024. 4. 21. 10:10
들어가며
- SSR 를 위해 Nuxt 를 하면서 결국엔 Node 서버까지 알아야 되는 상황에 처했다
- 이전에 공부하긴 했었지만 "대충" 훑기만 했었기 때문에 이게 뭔지 다시 톺아 보려고 한다
Node.js 가 생기게 된 배경
- 기존에 javascript 는 웹에서만 쓰이는 언어였다
- 나 첨 시작할 땐 언어라고 인정해 주지도 않았음
- 이때는 js 가 그저 html 을 예쁘게 만들어 주는 도구 정도였다
- chrome 에서 v8 엔진을 만들었는데, 이게 무지 빠른 엔진이었던 것이었다
- 그리고 그 당시 웹 서버들은 매 요청마다 별도의 스레드를 생성하고 있었는데, 그게 리소스를 넘 많이 소모하고 확장성에 한계가 있었음
- 따라서 비동기 이벤트 주도 방식을 도입한 Node.js 를 개발하게 된 것임
- I/O 작업이 블락되지 않아서 높은 성능을 제공함
Node.js 의 구성 요소
- Node.js 내부 라이브러리
- 내부 모듈이라고 생각하면 됨
- 파일 시스템 접근, http 서버 생성, 스트림 처리 등을 할 수 있음
- Node.js 의 API 를 형성하고 js 를 통해 접근해서 사용할 수 있음
- 대부분 비동기 방식으로 작성되어있기 때문에, 비동기 이벤트 주도 아키텍처를 잘 활용함
- v8 엔진
- 요 엔진으로 js 코드를 실행할 수 있음
- 기계어로 컴파일하고 실행을 함
- JIT 컴파일 방식을 사용하고 있기 때문에 js 를 빠르고 효율적으로 실행하는 핵심적인 역할을 함
- libuv 라이브러리
- 비동기 I/O 처리를 위한 라이브러리임
- Node.js 는 단일 스레드인데 비동기 방식으로 여러 I/O 작업을 동시에 처리할 수 있게끔 해줌
- 이벤트 루프와 비동기 네트워킹을 포함해서 파일 시스템 작업, DNS 작업 등을 비동기로 처리할 수 있음
-> 요기서 알아야 할 것이 v8 와 libuv 는 서로 다른 역할을 하기 때문에 둘 다 필요함
-> v8 으로 js 코드를 실행하는 것이고 libuv 로 Node.js 의 핵심인 비동기 I/O 처리를 할 수 있는 것임
Node.js 에서 코드가 실행되는 과정
- 코드 실행
- v8 엔진이 Node.js 로 작성된 js 코드를 읽고 실행함
- 이때 함수 호출, 변수 할당 같은 코드에 정의된 동작들이 수행됨 (동기적인 코드는 바로 처리)
- 비동기 작업
- 코드 중 비동기 작업 (파일 읽기 or 네트워크 요청 등) 이 요청되면 이 요청은 libuv 로 처리됨
- 작업이 완료될 때 실행될 콜백 함수가 등록됨
- 이벤트 루프
- libuv 가 이벤트 루프를 관리함
- 이벤트 루프는 비동기 작업의 완료 여부를 계속해서 확인하고,
작업이 완료되면 해당 콜백함수를 이벤트 큐에 넣음
- 콜백 처리
- 이벤트 루프는 호출 스택이 비어있을 때 (현재 실행 중인 코드가 없을 때)
이벤트 큐에서 콜백 함수를 호출 스택으로 이동시키고 실행함 - 이 과정은 큐의 FIFO (first in first out) 방식으로 동작함
- 이벤트 루프는 호출 스택이 비어있을 때 (현재 실행 중인 코드가 없을 때)
- 콜백 실행
- 호출 스택에서 콜백 함수가 실행되고 실행이 완료되면 스택에서 제거됨
여기서 드는 의문
- 그럼 구성 요소에 있던 Node.js 내부 모듈은 하는 일이 뭔지?
- v8 엔진과 libuv 라이브러리의 기능들을 결합해서 개발자가 편하게 사용할 수 있도록 함
- API 를 제공해 준다 생각하면 됨
- 그런 API 로 개발자가 서버를 생성하고 DB 통신 등의 작업이 가능하게 되는 거임
Node.js 이벤트 루프의 특징
- 페이즈 간의 순환
- 여러 페이즈를 순환함 (poll 등)
- 각 페이즈는 각 페이즈 종류의 작업을 처리하고, 이벤트 루프는 한 페이즈의 작업을 모두 처리한 후 다음 페이즈로 이동함
- 페이즈 내 작업 처리
- 한 페이즈에서 시작된 작업들은 그 페이즈 내에서 처리되어야 함
- 예를 들어서 timers 페이즈에서 처리되는 타이머 콜백들은 timers 페이즈 동안 모두 처리됨
- 이 작업은 일반적으로 각 페이즈의 큐에 등록된 순서에 따라 처리됨
- 작업 처리 시간
- 한 페이즈에서 처리해야 할 작업이 많아서 시간이 오래 걸리면, 이벤트 루프는 그 작업을 모두 처리할 때까지 그 페이즈에 머물게 됨
- 다른 페이즈로의 이동이 지연될 수 있음
- Starvation(굶주림)
- 한 페이즈에서 작업이 너무 오래 걸리면 starvation 현상을 일으킬 수 있음
- 이 말은 다른 페이즈의 작업이나 콜백 처리가 지연돼서 시스템의 반응성이 떨어질 수 있다는 것임
- 하지만 Node.js 는 특정 페이즈에서 처리할 수 있는 작업의 양을 제한둬서 한 사이클 내에서 다음 페이즈로 넘어갈 수 있도록 함
여기서 드는 의문 2
- 브라우저에서 동작하는 이벤트 루프와 다른 점은 뭔데?
- 브라우저에서의 이벤트 루프
- 브라우저의 이벤트 루프는 UI 이벤트, ajax 요청, setTimeout() 등을 처리하기 위해 설계됨
- 브라우저는 각 탭마다 자체 이벤트 루프를 가질 수 있기 때문에, 한 탭이 멈추더라도 다른 탭에는 영향을 미치지 않음
- microtask 큐를 가짐. 이 큐는 프로미스와 같은 작업이 완료된 후 바로 실행되어야 하는 콜백을 처리함
- 렌더링과 관련된 작업을 처리하기 위해 requestAnimationFrame(), layout, paint 와 같은 추가적인 메커니즘을 갖고 있음
- Node.js 의 이벤트 루프
- libuv 라이브러리에 의해 구현됨
- I/O 중심의 작업을 처리하기 위해 설계됨
- 단일 인스턴스에서 모든 클라리언트 요청을 처리하기 때문에 서버 측 애플리케이션에서 동시성을 다루는 데에 적합함
- process.nextTick() 과 Promise 의 resolve 의 콜백을 처리하기 위한 microtask 큐를 가지고, 이 큐는 브라우저의 microtsk 큐와 유사함
- 이벤트 루프가 여러 페이즈를 가지고, 각 페이즈는 특정 종류의 작업을 처리함 (브라우저 이벤트 루프보다 더 복잡함)
- UI 렌더링과 관련된 작업이 없기 때문에 렌더링과 관련된 페이즈는 없음
- 공통점
- 둘 다 비동기적인 작업을 처리하기 위해 이벤트 루프를 사용함
- microtask 큐를 가지고 있음
- 둘 다 태스크가 완료되면 콜백 함수를 실행하도록 스케줄링하는 메커니즘을 가지고 있음