본문 바로가기

javascript/nodejs

[nodejs] thread pool

참고 자료


node의 이벤트 루프가 싱글 스레드 기반으로 동작하기는 하지만, 내부적으로 들어가면 시간이 오래 걸리는 특정 작업(File I/O, DNS 등)의 경우 libuv 라이브러리가 제공하는 스레드 풀을 이용하여 처리한다.

https://docs.libuv.org/en/v1.x/threadpool.html#threadpool

 libuv에서 스레드 개수는 UV_THREADPOOL_SIZE 환경 변수로 나타나며 기본 값은 4이다. 이 값은 변경할 수 있으며, 최대 1024의 값을 가질 수 있다.

병렬성 수준

 nodejs는 os 모듈의 cpus().length 또는 availableParallelism() 함수를 통해 현재 컴퓨터에서 가능한 병렬 수준을 제공한다.

import { availableParallelism, cpus } from 'os';

console.log(availableParallelism());
console.log(cpus().length);

// 내 환경에서는
//8
//8

두 함수는 기대하는 역할이 조금 다르다. cpus( ) 함수는 현재 컴퓨터의 '물리적' 프로세서 정보를 가져오는 반면, avaliableParallelism( ) 함수는 현재 컴퓨터의 '논리적' 병렬 수준을 반환한다. 공식 문서에 따르면 이 함수는 libuv 라이브러리가 제공하는 uv_available_parallelism() 에 대한 간단한 래퍼 함수이다.

위 코드 흐름을 보면 알다시피, availableParallelism( )은 단순히 cpus().length의 래퍼 코드가 아니며 논리적 병렬 수준을 제시한다는 점에서 공식 문서에서도 병렬성을 구할 목적이라면 cpus().length 대신 availableParallelism 사용을 권고한다.

cpus의 흐름도 제시한다.

  • cpus는 getCPUs( ) 메서드의 정보를 가져와 배열로 변환하는 메서드
  • getCPUs( )는 C언어 기반 코어 메서드인 GetCPUInfo( )의 다른 이름으로 매핑된 것
  • GetCPUInfo( )는 C++ 기반으로 uv_cpu_info를 호출하여 정보를 얻은 후 배열 형태로 저장하고 반환

availableParallelism != UV_THREADPOOL_SIZE

헷갈리기 쉬운 부분이다. availableParallelism 함수가 논리적 병렬 수준을 반환하기는 하지만, 이게 현재 Node.js 환경에서 사용하고 있는 스레드 풀의 크기를 의미하지는 않는다. 스레드 풀의 크기는 UV_THREADPOOL_SIZE 환경 변수로 표현되고, 따로 건들이지 않으면 CPU 코어 수의 개수와 관계 없이 초기 값인 4를 유지한다.

 libuv에서 스레드 풀로 작동하는 것으로 알려진 crypto 모듈의 해싱 함수를 여러번 실행해서 이를 검증하자.

import { availableParallelism, cpus } from 'os';
console.log(availableParallelism());
console.log(cpus().length);
console.log(process.env.UV_THREADPOOL_SIZE);
const MAX_CALLS = 16;

const start = Date.now();
for (let i = 0; i < MAX_CALLS; i++) {
  pbkdf2("password", "salt", 100000, 512, "sha512", () => {
    console.log(`hash: ${i}, ${Date.now() - start}`);
  })
}

 

출력된 결과

정확히 해시 함수 4개마다 비슷한 종료 시간대를 가지는 모습을 볼 수 있다. availableParallelism( ) 함수가 알려준 논리적 병렬 수준은 8인데 비해 실제로는 4개의 스레드만 신나게 굴려서 4개의 논리적 코어가 놀고 있다.


libuv 스레드 풀 사이즈 늘리기

이 부분은 윈도우 환경과 리눅스 환경이 조금 다른데, 공통적으로 사용할 수 있는 방법은 UV_THREADPOOL_SIZE를 환경 변수로 등록하는 것이다.

윈도우에서 환경 변수로 등록한 모습
환경 변수 설정 후 실행한 결과

 환경 변수를 컴퓨터의 논리적 병렬 수준인 8로 설정한 후 다시 실행하니,  최종적인 종료 시간이 대략 1초 빨라진 모습을 볼 수 있었다. 즉, 환경 변수를 변경하면 해당 값이 Node.js 환경에 적용된다는 것을 알 수 있다.

 리눅스의 경우 process.env.UV_THREADPOOL_SIZE = 8 과 같이 소스코드 수준에서 병렬 수준을 지정할 수 있다.

'javascript > nodejs' 카테고리의 다른 글

[nodejs] libuv  (0) 2023.08.16
[nodejs] esbuild 전체 폴더 감시하기  (0) 2023.05.30
[nodejs] express-session typescript와 사용하기  (0) 2023.03.21
[nodejs] prisma ORM 라이브러리  (0) 2023.02.22
[nodejs] mysql2 라이브러리  (0) 2023.02.14