비동기 API를 이용할 때 Promise.all로 비동기 요청을 병렬 처리하는 경우가 많다.
그런데 병렬 처리시 어떤 비동기 API를 사용하느냐에 따라서 몇몇 요청이 지연되는 케이스가 생길 수도 있다.
비동기 API 처리 과정
Nodejs에서 비동기 처리는 libuv 라이브러리가 담당한다.
libuv는 비동기 요청이 들어오면 커널단에서 비동기적으로 처리가 가능한지 확인한다.
커널에서의 비동기 처리 가능 유무에 따라 다음과 같이 동작한다.
- 커널에서 비동기 처리 지원 O -> 메인스레드에서 커널에 비동기 작업 요청
- 커널에서 비동기 처리 지원 X -> 스레드풀의 스레드에서 커널에 동기 작업 요청
libuv는 비동기 요청에 대해서 non-blocking 방식으로 시스템콜을 날린다.
(non-blocking 방식이기 때문에 nodejs 애플리케이션 수준에서는 컨텍스트 스위칭이 일어나지 않는다.)
하지만 libuv에서 동기적으로 I/O 요청을 날리는 경우도 있는데 대표적으로 FileSystem이 있다.
FileSystem I/O
Nodejs 수준에서 비동기/논블로킹으로 동작하지만, libuv에서 동기/블로킹으로 요청하는 대표적인 케이스다.
Nodejs 개발자들은 이벤트루프를 blocking하지 않기 위해 fs.readFileSync 대신 fs.readFile을 사용한다.
하지만 위에서 언급했듯이 libuv의 FileSystem API는 비동기 I/O를 지원하지 않는다.
이는 libuv 문서에도 명시되어 있다.
Note 문단을 해석하면 다음과 같다.
libuv의 filesystem 작업은 socket 작업과 다릅니다. socket 작업은 OS에서 제공하는 non-blocking 작업을 사용합니다.
filesystem 작업은 내부적으로 blocking 함수를 사용하지만, 이러한 작업들은 스레드풀에서 호출하고 애플리케이션과 상호작용이 필요할 때 event loop에 등록된 관찰자에게 알립니다.
즉, 이벤트루프가 blocking되는 상황을 피하기 위해서 스레드풀의 스레드가 대신 blocking 되는 것이다.
FileSystem 요청을 Promise.all로 요청하면?
위에서 FileSystem을 호출하면 해당 요청을 스레드풀에게 위임된다고 하였다.
그렇다면 FileSystem 요청을 한번에 여러번 호출한다면 어떻게 될까?
결론부터 말하자면, 스레드풀의 스레드 개수만큼만 동시성 처리가 가능하다.
Nodejs의 스레드풀은 따로 지정하지 않는 이상 default로 4개가 생성된다.
파일시스템 요청이 5개고, 각 요청이 오래 걸리면 가장 늦게 요청된 1개의 작업은 지연될 것이다.
결론
사용자의 관점에서는 파일시스템을 수십 개 호출하지 않는 이상 성능상의 병목을 체감하기 어려울 것이라고 생각한다.
가능하면 FileSystem처럼 스레드풀을 점유하는 API는 과도하게 사용하지 않도록 적절히 조절하는 것이 좋을듯 하다.
뭐든지 적당히...가 중요한것 같다.
Reference
'BackEnd > Node.js' 카테고리의 다른 글
[JS] bytecode로 분석해보는 var, let, const 호이스팅 (0) | 2023.08.15 |
---|