백엔드/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 에서 코드가 실행되는 과정

  1. 코드 실행
    • v8 엔진이 Node.js 로 작성된 js 코드를 읽고 실행함
    • 이때 함수 호출, 변수 할당 같은 코드에 정의된 동작들이 수행됨 (동기적인 코드는 바로 처리)
  2. 비동기 작업
    • 코드 중 비동기 작업 (파일 읽기 or 네트워크 요청 등) 이 요청되면 이 요청은 libuv 로 처리됨
    • 작업이 완료될 때 실행될 콜백 함수가 등록됨
  3. 이벤트 루프
    • libuv 가 이벤트 루프를 관리함
    • 이벤트 루프는 비동기 작업의 완료 여부를 계속해서 확인하고,
      작업이 완료되면 해당 콜백함수를 이벤트 큐에 넣음
  4. 콜백 처리
    • 이벤트 루프는 호출 스택이 비어있을 때 (현재 실행 중인 코드가 없을 때)
      이벤트 큐에서 콜백 함수를 호출 스택으로 이동시키고 실행함
    • 이 과정은 큐의 FIFO (first in first out) 방식으로 동작함
  5. 콜백 실행
    • 호출 스택에서 콜백 함수가 실행되고 실행이 완료되면 스택에서 제거됨

여기서 드는 의문

  • 그럼 구성 요소에 있던 Node.js 내부 모듈은 하는 일이 뭔지?
    • v8 엔진과 libuv 라이브러리의 기능들을 결합해서 개발자가 편하게 사용할 수 있도록 함
    • API 를 제공해 준다 생각하면 됨
    • 그런 API 로 개발자가 서버를 생성하고 DB 통신 등의 작업이 가능하게 되는 거임

Node.js 이벤트 루프의 특징

  • 페이즈 간의 순환
    • 여러 페이즈를 순환함 (poll 등)
    • 각 페이즈는 각 페이즈 종류의 작업을 처리하고, 이벤트 루프는 한 페이즈의 작업을 모두 처리한 후 다음 페이즈로 이동함
  • 페이즈 내 작업 처리
    • 한 페이즈에서 시작된 작업들은 그 페이즈 내에서 처리되어야 함
    • 예를 들어서 timers 페이즈에서 처리되는 타이머 콜백들은 timers 페이즈 동안 모두 처리됨
    • 이 작업은 일반적으로 각 페이즈의 큐에 등록된 순서에 따라 처리됨
  • 작업 처리 시간
    • 한 페이즈에서 처리해야 할 작업이 많아서 시간이 오래 걸리면, 이벤트 루프는 그 작업을 모두 처리할 때까지 그 페이즈에 머물게 됨
    • 다른 페이즈로의 이동이 지연될 수 있음
  • Starvation(굶주림)
    • 한 페이즈에서 작업이 너무 오래 걸리면 starvation 현상을 일으킬 수 있음
    • 이 말은 다른 페이즈의 작업이나 콜백 처리가 지연돼서 시스템의 반응성이 떨어질 수 있다는 것임
    • 하지만 Node.js 는 특정 페이즈에서 처리할 수 있는 작업의 양을 제한둬서 한 사이클 내에서 다음 페이즈로 넘어갈 수 있도록 함

여기서 드는 의문 2

  • 브라우저에서 동작하는 이벤트 루프와 다른 점은 뭔데?
  1. 브라우저에서의 이벤트 루프
    • 브라우저의 이벤트 루프는 UI 이벤트, ajax 요청, setTimeout() 등을 처리하기 위해 설계됨
    • 브라우저는 각 탭마다 자체 이벤트 루프를 가질 수 있기 때문에, 한 탭이 멈추더라도 다른 탭에는 영향을 미치지 않음
    • microtask 큐를 가짐. 이 큐는 프로미스와 같은 작업이 완료된 후 바로 실행되어야 하는 콜백을 처리함
    • 렌더링과 관련된 작업을 처리하기 위해 requestAnimationFrame(), layout, paint 와 같은 추가적인 메커니즘을 갖고 있음
  2. Node.js 의 이벤트 루프
    • libuv 라이브러리에 의해 구현됨
    • I/O 중심의 작업을 처리하기 위해 설계됨
    • 단일 인스턴스에서 모든 클라리언트 요청을 처리하기 때문에 서버 측 애플리케이션에서 동시성을 다루는 데에 적합함
    • process.nextTick() 과 Promise 의 resolve 의 콜백을 처리하기 위한 microtask 큐를 가지고, 이 큐는 브라우저의 microtsk 큐와 유사함
    • 이벤트 루프가 여러 페이즈를 가지고, 각 페이즈는 특정 종류의 작업을 처리함 (브라우저 이벤트 루프보다 더 복잡함)
    • UI 렌더링과 관련된 작업이 없기 때문에 렌더링과 관련된 페이즈는 없음
  • 공통점
    • 둘 다 비동기적인 작업을 처리하기 위해 이벤트 루프를 사용함
    • microtask 큐를 가지고 있음
    • 둘 다 태스크가 완료되면 콜백 함수를 실행하도록 스케줄링하는 메커니즘을 가지고 있음