본문 바로가기

javascript/nodejs

[nodejs] libuv

이미지 출처: https://docs.libuv.org/en/v1.x/index.html

도움: chatgpt, stackoverflow 등. chatgpt는 부정확한 내용이 조금 있어서 구글링 도움을 많이 받음.


 노드는 비동기 + 이벤트 기반 모델을 기반으로 작동한다. 일반적으로 single thread, non-blocking I/O, event loop를 노드의 특징으로 설명하는데, 이러한 핵심 동작 원리는 내부적으로 C언어 기반으로 작성된 libuv가 수행한다.

libuv

libuv는 Node.js의 이벤트 기반 비동기 I/O 모델을 지원하기 위해 작성된 크로스 플랫폼 라이브러리로, 노드는 내부적으로 시간이 오래 걸리는 작업(File I/O, DNS 연산 등)을 libuv에 위임하여 처리한다. 해당 작업 중 일부는 libuv 내부 정의된 스레드풀을 이용하여 처리하고, 일부는 운영체제에게 위임한다.

내부적으로 들어가면 파일 I/O나 네트워킹이나 운영체제의 시스템 콜 호출에 의해 처리되지만, 파일 I/O의 경우 크로스 플랫폼에서 동작하는 비동기 디스크 API가 마땅치 않아서 스레드 풀에서 blocking I/O 방식으로 처리한다고 한다.

libuv를 구성하는 여러 기능들

 node의 핵심 동작 구조인 이벤트 루프 역시 libuv에 의해 지원된다. 이벤트 루프 동작 단계는 다음과 같다.

libuv의 이벤트 루프 동작 방식

  1. 이벤트 루프가 시작될 때 "현재(now)" 시간 개념을 초기화한다. libuv의 어떤 작업이나 이벤트는 현재 시간과 비교하여 언제 실행될지 결정되는데, 이를 위해 초기 시간을 지정하는 부분이다. ( 초기 시간 설정 )
    ex) 타이머 작업은 현재 시간 + 시간 간격이 되는 시점에 실행되어야 하므로, 초기 시간을 알 필요가 있다.
  2. 루프가 UV_RUN_DEFAULT로 동작하면 Due timer이 동작한다. due timer은 libuv에서 제공하는 타이머 기능 중 하나로, 특정 시간이 지난 후 작업을 예약하고 실행할 수 있도록 도와주며, 특정 간격 이후 작업을 실행하도록 계획하는 데 사용된다. ( 타이머 실행 )
  3. 루프가 활성 상태(loop alive)라면 루프를 반복, 아니면 종료한다. 루프는 active / ref'd 핸들, active request 또는 close handle이 활성 상태인 경우 지속된다.
  4. Pending Callback을 호출한다. I/O 콜백은 일반적으로 I/O 풀링 직후에 실행되지만, 이벤트 루프 반복을 위해 연기되기도 한다. 이렇듯 이전 루프에서 I/O 콜백 실행이 연기된 경우 현재 루프의 Pending Callback 단계에서 처리된다.
  5. Idle Handle이 호출된다. 이름(idle = 유휴)과는 달리 루프가 반복될 때마다 콜백을 호출하며, I/O blocking 대신 zero timeout pool을 수행하는 점이 perpare handle과 다른 점이라고 한다.
  6. Prepare Handle이 호출된다. 이 핸들은 I/O 블로킹 전에 콜백을 호출한다.
  7. 폴링 제한 시간이 계산된다. 계산된 시간은 I/O polling에 사용된다. ( 자세한 규칙은 공식 문서 참고 )
  8. I/O 작업을 위해 루프가 블록된다. 이전 단계에서 계산된 폴링 제한 시간 만큼 지속되며, read/write 작업 I/O 관련 핸들은 이 시점에 콜백을 호출한다.
  9. Check Handle이 호출된다. I/O 작업에 의해 루프가 블록되자마자 자신의 콜백을 호출하며, 근본적으로 Prepare Handle과 반대 위치에 있다고 한다.
  10. Close Callback이 호출된다. 핸들이 uv_close( ) 에 의해 닫히면 close callback을 호출한다.
  11. 루프의 "현재(now)" 시간 개념이 업데이트된다.
  12. Due timer이 동작한다. 루프의 현재 시간은 다음 이벤트 루프 시작 전까지 갱신되지 않으므로, 루프 동작 도중에 타이머가 만료되더라도 다음 이벤트 루프 시작 전까지는 실행되지 않는다.
  13. 반복이 종료된다.

※ 파일 I/O는 스레드 풀에 의해 병렬 처리될 수 있지만 네트워크 I/O는 항상 단일 스레드, 즉 이벤트 루프에서 처리된다( 시스템 API 기반). Node.js에서 스레드 풀에서 처리되도록 정의된 것은 DNS lookup, fs, crypto, zlib 라이브러리 정도.