Rust로 TypeScript 런타임을 구축하여 성능을 9배 향상시키다!
Encore는 Go 프레임워크에서 시작하여 TypeScript 지원을 위해 Rust로 런타임을 재구축함
Rust를 통해 HTTP 요청 처리, 데이터베이스 연결 관리, Pub/Sub, 추적(Tracing) 등 인프라 레이어를 멀티 스레드로 처리하여 성능을 향상시킴
NAPI 바인딩(NAPI Bindings)을 사용하여 Rust와 Node.js 간의 상호 작용을 구현하고, Pingora를 활용하여 API 게이트웨이를 내장함
오픈텔레메트리(OpenTelemetry) 어댑터를 늦게 도입한 점과 에러 핸들링(Error Handling) 개선의 필요성을 언급함
Rust 런타임 채택의 기술적 배경
Encore는 TypeScript 지원을 위해 Rust를 선택한 주요 이유로 재사용성(Reusability)과 성능 향상을 꼽았다. Rust를 통해 인프라 관련 로직을 한 번 작성하고 여러 언어 런타임에 바인딩하여 중복 구현을 방지했다. 특히, Node.js의 싱글 스레드 환경에서 벗어나 tokio를 활용한 멀티 스레드 처리를 통해 HTTP 요청 처리, 데이터베이스 연결 관리 등에서 성능 이점을 확보했다.
Node.js와 Rust 간의 상호 작용
Node.js와 Rust 간의 상호 작용을 위해 NAPI(Node.js API) 바인딩을 사용했다. Rust에서 JavaScript 함수를 호출하고 반환 값을 캡처하기 위해 napi-rs의 ThreadSafeFunction을 포크(Fork)하여 기능을 확장했다. 또한, 비동기 처리(Asynchronous Processing)를 위해 Promise를 감지하고 tokio 채널을 통해 결과를 Rust로 전달하는 방식을 사용했다. 이러한 방식은 Node.js의 이벤트 루프와 Rust의 비동기 모델을 연결하는 핵심적인 역할을 한다.
Pingora를 활용한 API 게이트웨이 내장
Encore는 Cloudflare의 오픈 소스 HTTP 프록시 라이브러리인 Pingora를 API 게이트웨이로 활용하여 런타임에 내장했다. Pingora는 별도의 프로세스 없이 런타임 내에서 실행되므로, 인증, CORS, 요청 유효성 검사 등을 효율적으로 처리할 수 있다. 특히, TypeScript로 작성된 사용자 정의 인증 핸들러를 게이트웨이 내에서 직접 실행할 수 있어 데이터 직렬화(Data Serialization) 및 IPC(Inter-Process Communication) 오버헤드를 줄였다.
클라우드 제공업체 추상화 및 트레이싱 시스템
Encore는 NSQ, GCP Pub/Sub, AWS SNS+SQS 등 여러 클라우드 제공업체를 지원하기 위해 트레이트 객체(Trait Objects)를 활용하여 코드를 추상화했다. 각 제공업체의 특성을 개별 모듈에 캡슐화하고, 런타임 설정에 따라 적절한 구현체를 선택하도록 설계했다. 또한, 사용자 정의 바이너리 트레이스 프로토콜을 구현하여 성능을 최적화하고, OpenTelemetry 어댑터를 통해 기존 관찰성 스택과의 통합을 지원한다.
성능 벤치마크 및 개선점
Encore는 Rust 런타임을 통해 Express.js 대비 9배 높은 처리량을 보이며, 80% 낮은 지연 시간을 기록했다. 이러한 성능 향상은 Rust 기반의 요청 유효성 검사(Request Validation)를 통해 이루어졌다. 저자는 에러 컨텍스트(Error Context) 개선과 OpenTelemetry 어댑터의 조기 도입, 스냅샷 테스트(Snapshot Testing)의 중요성을 강조하며, Go와의 FFI(Foreign Function Interface) 통합에 대한 어려움을 언급했다.