[NestJS] 비동기의 핵심, 이벤트 루프 + libuv 파헤치기 (2편)
카테고리
프로그래밍/소프트웨어 개발
서브카테고리
개발 툴
대상자
Node.js 및 NestJS를 사용하는 백엔드 개발자, 성능 최적화에 관심 있는 개발자
난이도: 중급 ~ 고급 (이벤트 루프, libuv, 병렬 처리 이해 필요)
핵심 요약
- *Node.js의 싱글 스레드 구조는 I/O 작업에 강하지만 CPU 집약 작업에 약하다.**
- libuv는 비동기/논블로킹 I/O 처리를 담당하는 핵심 라이브러리로, 운영체제 최적화 API(IOCP, epoll, kqueue)를 활용한다.
- Worker Threads는 순수 CPU 연산(암호화, 이미지 처리 등)을 병렬 처리하고, PM2 클러스터 모드는 멀티코어를 활용해 부하 분산 가능.
- Thread Pool은 I/O 작업 처리에 사용되며,
UV_THREADPOOL_SIZE
환경 변수로 크기 조절 가능.
섹션별 세부 요약
- 왜 CPU 집약 작업에 약할까?
- Node.js는 싱글 스레드 + 이벤트 루프 구조로, CPU 집약 작업은 메인 스레드를 블로킹해 응답 지연을 유발.
- 예:
for (let i = 0; i < 1e9; i++) { Math.sqrt(i); }
같은 반복문은 이벤트 루프를 멈추게 함. - 해결 방법: Worker Threads 또는 PM2 클러스터 모드 활용.
- 왜 I/O 작업은 장점일까?
- libuv가 OS 최적화 API를 사용해 I/O 작업을 비동기 처리.
- 예: 파일 읽기, 네트워크 요청, DNS 조회 등은 libuv가 시스템 API로 처리해 블로킹 방지.
- Node.js의 이벤트 루프는 libuv의 구현체로, JavaScript 로직은 V8 엔진을 통해 처리.
- libuv란 무엇인가?
- C 기반 라이브러리로, Node.js의 비동기 처리 핵심 엔진.
- 기능: 네트워크, 파일 I/O, 타이머, DNS 등 모든 비동기 작업 처리.
- OS 최적화 API 사용:
- Windows: IOCP
- Linux: epoll
- macOS: kqueue
- Spring과의 차이: Spring은 select 사용 vs libuv는 더 효율적인 epoll 사용.
- libuv의 내부 동작 원리
uv_run()
함수가 이벤트 루프를 실행하며,UV_RUN_DEFAULT
모드로 지속적으로 처리.- 비동기 작업 처리 흐름:
- JavaScript 코드 실행 →
- libuv 호출 →
- 작업 분류(비동기/동기) →
- 시스템 API 또는 Thread Pool 위임 →
- 콜백 등록 →
- 이벤트 루프가 콜백 실행.
- Thread Pool의 이해
- 기본적으로 4개의 워커 스레드가 존재,
UV_THREADPOOL_SIZE
로 조절 가능. - 처리 가능한 작업:
- crypto.pbkdf2
, 파일 시스템 I/O, zlib 압축, DNS 조회 등.
- 처리 불가능한 작업: 순수 JavaScript 연산(예:
Math.sqrt
반복). - 작업 처리 방식: FIFO 큐 기반, 스레드 완료 시 대기 중 작업 처리.
결론
- *NestJS에서 성능 최적화 전략:**
- CPU 집약 작업: Worker Threads
또는 PM2 -i max
클러스터 모드 사용.
- I/O 작업: UV_THREADPOOL_SIZE
값을 늘려 효율적으로 처리.
- *핵심 팁:** libuv의 OS 최적화 API와 Thread Pool을 이해하면 Node.js의 비동기 메커니즘을 효과적으로 설계 및 최적화할 수 있다.