Home What is a Node.js
Post
Cancel

What is a Node.js

What is a Node.js

목차


1. Core concepts

1.1. 서버

  • 노드는 서버 애플리케이션을 실행하는데 제일 많이 사용
  • 서버란 무엇이며, 어떤 역할을 하는가?
    • 서버는 네트워크를 통해 클라이언트에 정보나 서비스를 제공하는 컴퓨터 또는 프로그램을 의미
    • 클라이언트란 요청을 보내는 주체 (ex. 브라우저, 데스크톱 프로그램, 모바일 앱, 다른 서버에 요청을 보내는 서버 등)
    • 사용자 또는 서비스의 데이터가 생성될 경우, 이 데이터를 어딘가에 저장하고, 그 어딘가에서 클라이언트로 데이터를 받아오는 역할
  • 요약
    • 서버는 클라이언트의 요청에 대해 응답
    • 노드는 자바스크립트 애플리케이션이 서버로서 가능하기 위한 도구를 제공하므로 서버 역할을 수행 가능

1.2. 특성 및 라이브러리

1.3. 이벤트 기반

  • 이벤트 기반(event-driven)이란 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식
    • 이벤트 예시 : 클릭, 네트워크 요청 등
  • 이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할지 미리 등록해두어야 함
  • 노드는 이벤트 기반 방식으로 동작
  • 노드는 발생한 이벤트가 없거나 발생했던 이벤트를 다 처리하면 다음 이벤트가 발생할 때까지 대기
  • 이벤트 루프
    • 이벤트 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정
    • 노드가 종료될 때까지 이벤트 처리를 위한 작업을 반복하므로 루프
    • 호출 스택을 통해서 실행
  • 태스크 큐
    • 이벤트 발생 후 호출되어야 할 콜백 함수들이 기다리는 공간
    • 콜백 라고도 부름
  • 백그라운드
    • 타이머 or I/O 작업 콜백 or 이벤트 리스너들이 대기하는 곳
  • 이벤트 루프는 호출 스택이 비어있을 때만 태스크 큐에 있는 함수를 호출 스택으로 가져옴

1.4. 이벤트 루프

  • 이벤트 루프란?
    • 이벤트 루프는 가능하다면 언제나 시스템 커널에 작업을 떠넘겨서 Node.js가 논 블로킹 I/O 작업을 수행하도록 해줌
    • 대부분의 현대 커널은 멀티 스레드이므로 백그라운드에서 다수의 작업을 실행
    • 이러한 작업 중 하나가 완료되면 커널이 Node.js에게 알려주어 적절한 콜백을 poll 큐에 추가할 수 있게 하여 결국 실행
  • 이벤트 루프 설명
    • 이벤트 루프의 작업 순서의 간단한 개요
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
         ┌───────────────────────────┐
      ┌─>│           timers          │
      │  └─────────────┬─────────────┘
      │  ┌─────────────┴─────────────┐
      │  │     pending callbacks     │
      │  └─────────────┬─────────────┘
      │  ┌─────────────┴─────────────┐
      │  │       idle, prepare       │
      │  └─────────────┬─────────────┘      ┌───────────────┐
      │  ┌─────────────┴─────────────┐      │   incoming:   │
      │  │           poll            │<─────┤  connections, │
      │  └─────────────┬─────────────┘      │   data, etc.  │
      │  ┌─────────────┴─────────────┐      └───────────────┘
      │  │           check           │
      │  └─────────────┬─────────────┘
      │  ┌─────────────┴─────────────┐
      └──┤      close callbacks      │
         └───────────────────────────┘
      
    • 각 단계는 실행할 콜백의 FIFO 큐를 가짐
    • 각 단계는 자신만의 방법에 제한적이므로 보통 이벤트 루프가 해당 단계에 진입하면 해당 단계에 한정된 작업을 수행하고 큐를 모두 소진하거나 콜백의 최대 개수를 실행할 때까지 해당 단계의 큐에서 콜백을 실행
    • 큐를 모두 소진하거나 콜백 제한에 이르면 이벤트 루프는 다음 단계로 이동
    • 이러한 작업이 또 다른 작업을 스케줄링하거나 poll 단계에서 처리된 새로운 이벤트가 커널에 의해 큐에 추가될 수 있으므로 폴링 이벤트를 처리하면서 poll 이벤트를 큐에 추가할 수 있음
    • 그 결과 오래 실행되는 콜백은 poll 단계가 타이머의 한계 시점보다 훨씬 더 오래 실행되도록 할 수 있음
  • 단계 개요
    • timers: 이 단계는 setTimeout()setInterval()로 스케줄링한 콜백을 실행
    • pending callbacks: 다음 루프 반복으로 연기된 I/O 콜백들을 실행
    • idle, prepare: 내부용으로만 사용
    • poll: 새로운 I/O 이벤트를 가져옴
      • I/O와 연관된 콜백(클로즈 콜백, 타이머로 스케줄링된 콜백, setImmediate()를 제외한 거의 모든 콜백)을 실행
      • 적절한 시기에 node는 여기서 블록
    • check: setImmediate() 콜백은 여기서 호출
    • close callbacks: 일부 close 콜백들, 예를 들어 socket.on('close', ...).
    • 이벤트 루프가 실행하는 사이 Node.js는 다른 비동기 I/O나 타이머를 기다리고 있는지 확인하고 기다리고 있는 것이 없다면 깔끔하게 종료

1.5. 논블로킹 I/O

  • 이벤트 루프를 잘 활용하면 오래 걸리는 작업을 효율적으로 처리할 수 있음
  • 논블로킹 방식
    • 오래 걸리는 함수를 백그라운드로 보내서 다음 코드가 먼저 실행되게 하고, 그 함수가 다시 태스크 큐를 거쳐 호출 스택으로 올라오기를 기다리는 방식
  • 논블로킹이란?
    • 이전 작업이 완료될 때까지 멈추지 않고 다음 작업을 수행함을 의미
  • 하지만, 싱글 스레드라는 한계 때문에 자바스크립트의 모든 코드가 이 방식으로 시간적 이득을 볼 수 있는 것은 아님
  • 현재 노드 프로세스 외의 다른 컴퓨팅 작원을 사용할 수 있는 I/O 작업이 주로 시간적 이득을 많이 봄
  • I/O 란?
    • 입력(input)/출력(output)을 의미
    • 파일 시스템 접근이나 네트워크 요청같은 작업이 I/O의 일종
    • I/O 작업을 할 때 노드는 논블로킹 방식으로 동작
  • 동기와 비동기, 블로킹과 논블로킹
    • 동기와 비동기 : 함수가 바로 return 되는지 여부
    • 블로킹과 논블로킹 : 백그라운드 작업 완료 여부
  • 노드에서는 동기-블로킹 방식과 비동기-논블로킹 방식이 대부분
  • 동기-블로킹 방식
    • 백그라운드 작업 완료 여부를 계속 확인하여, 호출한 함수가 바로 return 되지 않고 백그라운드 작업이 끝나야 return
  • 비동기-논블로킹 방식
    • 호출한 함수가 바로 return 되어 다음 작업으로 넘어가고, 백그라운드 작업 완료 여부는 신경 쓰지 않고 나중에 백그라운드가 알림을 줄 때 처리
  • 동기-논블로킹, 비동기-블로킹 방식은 없다고 보면 됨

1.6. 블로킹 vs 논블로킹

  • 블로킹 (blocking)
    • Node.js 프로세스에서 추가적인 JavaScript 의 실행을 위해 JavaScript 가 아닌 작업이 완료될 때까지 기다려야만 하는 상황
      • 이벤트 루프가 블로킹 작업을 하는 동안 JavaScript 실행을 계속할 수 없기 때문에
    • I/O 등의 JavaScript가 아닌 작업을 기다리는 것보다 CPU 집약적인 작업 때문에 나쁜 성능을 보여주는 JavaScript는 보통 블로킹이라고 부르지 않음
    • libuv를 사용하는 Node.js 표준 라이브러리의 동기 메서드가 가장 대표적인 블로킹 작업
    • 네이티브 모듈도 블로킹 메서드를 가질 수 있음
    • Node.js 표준 라이브러리의 모든 I/O 메서드는 논블로킹인 비동기 방식을 제공하고 콜백 함수를 받음
    • 일부 메서드는 같은 작업을 하는 블로킹 메서드도 가지는데 이는 이름 마지막에 Sync가 붙음
  • 코드 비교
    • 블로킹 메서드는 동기로 실행되고 논블로킹 메서드는 비동기로 실행
    • 파일 시스템 모듈을 사용할 때
      • 동기
        • 1
          2
          3
          4
          
          const fs = require("fs");
          const data = fs.readFileSync("/file.md"); // 파일을 읽을 때까지 여기서 블로킹됩니다.
          console.log(data);
          // moreWork();는 console.log 이후 실행될 것입니다.
          
        • 두 번째 줄에서 전체 파일을 읽을 때까지 다른 JavaScript 실행이 블로킹되는 단점
      • 비동기
        • 1
          2
          3
          4
          5
          6
          
          const fs = require("fs");
          fs.readFile("/file.md", (err, data) => {
            if (err) throw err;
            console.log(data);
          });
          // moreWork();는 console.log 이전에 실행될 것입니다.
          
  • 동시성과 스루풋
    • 동시성
      • 다른 작업이 완료된 후에 JavaScript 콜백 함수를 실행하는 이벤트 루프의 능력을 의미
      • 동시에 실행되어야 하는 모든 코드는 I/O 등의 JavaScript가 아닌 작업이 일어나는 동안 이벤트 루프가 계속 실행될 수 있도록 해야함
  • 블로킹과 논블로킹 코드를 섞을 때의 위험성
    • 잘못된 예시
      • 1
        2
        3
        4
        5
        6
        
        const fs = require("fs");
        fs.readFile("/file.md", (err, data) => {
          if (err) throw err;
          console.log(data);
        });
        fs.unlinkSync("/file.md");
        
      • fs.unlinkSync()가 fs.readFile()보다 먼저 실행될 수 있으므로 실제 file.md를 읽기 전에 파일을 제거할 수 있음
    • 수정된 예시
      • 1
        2
        3
        4
        5
        6
        7
        8
        
        const fs = require("fs");
        fs.readFile("/file.md", (readFileErr, data) => {
          if (readFileErr) throw readFileErr;
          console.log(data);
          fs.unlink("/file.md", (unlinkErr) => {
            if (unlinkErr) throw unlinkErr;
          });
        });
        
      • fs.readFile()의 콜백에서 fs.unlink()를 논블로킹으로 호출하도록 해서 작업 순서가 올바르도록 보장

1.7. 싱글 스레드

  • 노드는 싱글 스레드, 논블로킹 모델
  • 프로세스
    • 운영체제에서 할당하는 작업의 단위
    • 프로세스 간에는 메모리 등의 자원 공유 x
  • 스레드
    • 프로세스 내에서 실행되는 흐름의 단위
    • 하나의 프로세스는 여러 개의 스레드를 가질 수 있음
    • 스레드들은 부모 프로세스의 자원을 공유
      • 즉, 같은 메모리에 접근 o
  • 노드 프로세스도 내부적으로는 스레드를 여러 개 가지고 있으나, 직접 제어할 수 있는 스레드는 하나뿐
  • 노드는 프로세스 자체를 복사해 여러 작업을 동시에 처리하는 멀티 프로세스 방식을 선택
    • 자바스크립트 언어 자체가 싱글 스레드 특성을 지니기 때문

2. 서버로서의 노드

  • 노드가 싱글 스레드, 논블로킹 모델이므로, 노드 서버 또한 싱글 스레드, 논블로킹 모델
  • I/O가 많은 작업에 적합
    • libuv 라이브러리를 사용하여 I/O 작업을 논블로킹 방식으로 처리
    • 스레드 하나가 많은 수의 I/O 를 혼자서도 감당 가능
    • But, CPU 부하가 큰 작업에는 적합 x
      • CPU 연산을 많이 요구하면 블로킹이 발생해 스레드 하나가 감당하기 어려움
  • 싱글 스레드 방식의 프로그래밍은 멀티 스레드 방식보다 상대적으로 쉬움
    • 에러를 제대로 처리하지 못하면 서버 전체가 멈추므로 잘 관리
  • 웹 서버가 내장
    • But, 서버 규모가 커지면 결국 nginx 등의 웹 서버를 노드 서버와 연결
  • 노드는 생산성이 좋지만, Go 처럼 비동기에 강점을 보이는 언어나 nginx 처럼 정적 파일 제공, 로드 밸런싱에 특화된 서버에 비해서는 속도가 느림
    • But, 극단적인 성능이 필요하지 않다면 노드의 생산성으로 어느 정도 극복 가능
  • 데이터를 JSON으로 주고 받는데, JSON이 자바스크립트 형식이어서 노드에서도 쉽게 처리 가능
  • 장점단점
    [싱글 스레드] 컴퓨터 자원을 적게 사용함[싱글 스레드] CPU 코어를 하나만 사용함
    I/O 작업이 많은 서버로 적합CPU 작업이 많은 서버로는 부적합
    멀티 스레드 방식보다 쉬움하나뿐인 스레드가 멈추지 않도록 관리
    웹 서버가 내장서버 규모가 커졌을 때 관리가 어려움
    자바스크립트 사용어중간한 성능(?)
    JSON 형식과 호환하기 쉬움 
  • 어디에 적합할까?
    • 개수는 많지만 크기는 작은 데이터를 실시간으로 주고 받는 곳
      • 네트워크나 데이터베이스, 디스크 작업 같은 I/O 에 특화되어 있기 때문
    • 실시간 채팅 애플리케이션이나 주식 차트, JSON 데이터를 제공하는 API 서버가 노드를 많이 사용
  • 어디에 부적합할까?
    • 이미지나 비디오 처리, 대규모 데이터 처리 같이 CPU를 많이 사용하는 작업을 위한 서버로는 x
    • AWS Lambda 나 Google Cloud Functions 같은 서비스에서 노드로 CPU를 많이 사용하는 작업을 처리하는 것을 지원

3. References

This post is licensed under CC BY 4.0 by the author.

Knuth's Optimization

프레임워크와 라이브러리의 차이점