Node.js 워커 스레드, 싱글 스레드(Single Thread)의 한계를 극복하다!
Node.js의 싱글 스레드(Single Thread) 환경에서 CPU를 많이 사용하는 코드가 이벤트 루프(Event Loop)를 블로킹(Blocking)하여 문제 발생
Inngest Connect의 WebSocket 연결(WebSocket Connection) 유지 과정에서 워커 스레드(Worker Thread)를 활용하여 이벤트 루프(Event Loop) 블로킹 문제 해결
Node.js 워커 스레드는 다른 언어의 스레딩 모델과 달리 독립적인 프로그램(Independent Program) 형태로 설계되어 메시지 전달(Message Passing) 방식으로 통신
번들러(Bundler) 설정 및 메모리 오버헤드(Memory Overhead) 등 워커 스레드 사용 시 고려해야 할 제약 사항(Constraints) 존재
이벤트 루프(Event Loop) 블로킹(Blocking) 문제와 워커 스레드(Worker Threads) 도입
Node.js는 단일 스레드 환경에서 이벤트 루프(Event Loop)를 기반으로 비동기 I/O를 처리한다. 하지만 CPU를 많이 사용하는 코드가 실행되면 이벤트 루프가 블로킹되어 타이머(Timer) 및 네트워크 콜백(Network Callback)이 실행되지 않는 문제가 발생한다. Inngest Connect는 이러한 문제 해결을 위해 워커 스레드(Worker Threads)를 도입하여 이벤트 루프(Event Loop) 블로킹(Blocking) 문제를 해결했다.
Node.js 워커 스레드(Worker Threads)의 특징과 제약 사항
Node.js 워커 스레드는 다른 언어의 스레딩 모델과 차이점을 보인다. Go, Rust, Python과 달리 함수를 직접 전달할 수 없고, 별도의 파일로 정의된 독립적인 프로그램 형태로 동작한다. 또한, 구조적 복제 알고리즘(Structured Clone Algorithm)을 사용하여 데이터를 직렬화(Serialization) 및 역직렬화(Deserialization)하므로, 대용량 데이터 전송 시 성능 저하가 발생할 수 있다. 번들러(Bundler) 설정 및 메모리 오버헤드(Memory Overhead) 또한 고려해야 할 사항이다.
Inngest Connect의 워커 스레드(Worker Threads) 적용 사례
Inngest Connect는 WebSocket 연결(WebSocket Connection)을 통해 서버와 지속적인 통신을 유지한다. 기존에는 메인 스레드에서 모든 작업을 처리하여 CPU 사용량이 높은 작업 시 하트비트(Heartbeat)가 전송되지 않아 연결이 끊기는 문제가 발생했다. 워커 스레드 도입 후, WebSocket 연결(WebSocket Connection) 및 하트비트(Heartbeat) 로직을 워커 스레드로 분리하여 메인 스레드의 블로킹(Blocking)을 방지하고 안정성을 확보했다.
메시지 전달(Message Passing) 기반 통신 방식
Node.js 워커 스레드는 메시지 전달(Message Passing) 방식으로 통신하며, 데이터는 구조적 복제 알고리즘(Structured Clone Algorithm)을 통해 직렬화되어 전달된다. Inngest Connect에서는 로깅(Logging)과 같은 사용자 정의 객체(User-provided Objects)를 워커 스레드에서 직접 사용할 수 없어, 메인 스레드로 메시지를 전달하여 처리하는 방식을 사용했다. 이러한 방식은 워커 스레드와 메인 스레드 간의 데이터 격리 아키텍처(Data Isolation Architecture)를 유지하면서 유연성을 확보하는 데 기여한다.