ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [NodeJS] Libuv ThreadPool 과 비동기 처리 관계 알아보기 ( UV_THREADPOOL_SIZE )
    Language/Nodejs 2024. 8. 13. 06:26
    반응형

     

    - 목차

     

    Libuv Thread Pool 이란 ?

    Node.js 는 하나의 Thread 에서 동작하는 구조로 유명하지만 실제로는 libuv 의 Thread Pool 에 의해서 여러 쓰레드들이 함께 실행됩니다.

    특히 Node.js 에 내장된 비동기 함수들이 여러 Thread Pool 에서 멀티 쓰레드 구조로 실행됩니다.

    실제로 Node.js 프로세스를 실행하게 되면 아래와 같이 여러개의 Thread 가 확인됩니다.

    root@2153f4656997:/usr/src/app# ps -T -p 29
      PID  SPID TTY          TIME CMD
       29    29 pts/0    00:00:00 node
       29    30 pts/0    00:00:00 node
       29    31 pts/0    00:00:00 node
       29    32 pts/0    00:00:00 node
       29    33 pts/0    00:00:00 node
       29    34 pts/0    00:00:00 node
       29    35 pts/0    00:00:00 node
       29    36 pts/0    00:00:00 node

     

    아래의 이미지는 Node.js Process 의 Thread 구조입니다.

    Libuv 모듈에 의해서 아래와 같이 여러 개의 Thread 들이 생성 및 실행되며, 이는 UV_THREADPOOL_SIZE 환경 변수에 의해서 결정됩니다.

     

    출처 : https://levelup.gitconnected.com/nodejs-runtime-environment-libuv-library-event-loop-thread-pool-5f5ecadc0318

     

     

    일반적으로 fs 모듈에서 제공되는 여러 함수들이 Thread Pool 에서 실행됩니다.

    그리고 Libuv Thread 에서 실행된 결과가 Event Loop 를 통해서 Main Thread 로 이동되는 구조입니다.

    그래서 Libuv Thread Pool 에 의해서 비동기 처리 로직들이 동시 수행이 가능하지만,

    처리된 결과가 Event Loop 을 통해서 Main Thread 에서 처리되기에 이 부분에서 병목이 발생할 수 있습니다.

     

     

    UV_THREADPOOL_SIZE 와 처리 속도 관계.

    Node.js 에서 제공되는 내장 비동기 함수들은 C 언어로 작성됩니다.

    그리고 이 함수들은 내부적으로 Libuv 모듈의 Thread 에서 실행되도록 구성됩니다.

    특히 File System 과 관련된 fs 모듈의 함수들이 이와 관련 깊습니다.

     

    10mb 정도의 파일을 처리하는 소요되는 시간을 Thread Pool Size 와 관련하여 실험해보도록 합니다.

     

     

    Thread Pool 사이즈와 벤치마킹.

    아래의 명령어는 1개 / 10개의 Thread 로 10MB 의 파일을 50 회 처리하는데에 걸리는 시간을 출력합니다. 

    export UV_THREADPOOL_SIZE=1 && node benchmark.js
    export UV_THREADPOOL_SIZE=10 && node benchmark.js
    UV_THREADPOOL_SIZE = 1
    total-time: 457.736ms
    
    UV_THREADPOOL_SIZE = 10
    total-time: 38.487ms

     

    소요되는 시간은 위의 결과와 같이 10배 정도 차이가 납니다.

    리소스 환경에 따라서 결과를 다를 순 있겠지만, File System 과 같이 비동기 처리 함수들은 Thread Pool 갯수 설정에 영향을 받게 됩니다. 

     

     

    하지만 File System 이외에 사용할 수 있는 경우는 ?

    Node.js 는 강력한 Network 통신은 Libuv Thread Pool 을 사용하지는 않고, Kernel 의 Epoll 과 관련된 시스템 콜을 사용합니다.

    즉, TCP 소켓에 데이터가 추가된다면 이를 커널이 처리하고 Node.js Process 의 Event Loop 에게 넘겨주게 됩니다.

    따라서 네트워크 통신에서는 Libuv Thread Pool 의 득을 볼 순 없습니다.

     

    Node.js 에서 File System 과 관련된 처리를 적용하는 Application 은 드물기 때문에 Thread Pool 사이즈를 최적화하는 것보다

    Multi Processing 구조로 여러 Node.js Process 를 스케일 아웃하는 방식이 적합해 보입니다.

     

     

    관련된 명령어.

    yes "Hello World" | head -c 10M > testfile.log
    
    cat <<EOF> benchmark.js
    const fs = require("fs");
    const path = "./testfile.log";
    
    const totalTasks = 50;
    let completed = 0;
    
    console.log("UV_THREADPOOL_SIZE =", process.env.UV_THREADPOOL_SIZE || "default (4)");
    console.time("total-time");
    
    for (let i = 0; i < totalTasks; i++) {
      fs.readFile(path, () => {
        completed++;
        if (completed === totalTasks) {
          console.timeEnd("total-time");
        }
      });
    }
    EOF
    
    export UV_THREADPOOL_SIZE=1 && node benchmark.js
    export UV_THREADPOOL_SIZE=10 && node benchmark.js

     

    반응형
Designed by Tistory.