Rust로 Node.js Native Addon 만들어보기

배경

이미지 리사이징 API의 응답 속도가 문제였다. Sharp 라이브러리를 쓰고 있었지만, 특정 필터 처리에서 병목이 발생했다. C++로 작성할까 하다가 최근 관심있던 Rust로 시도해봤다.

Neon 사용

neon-bindings를 사용하면 Rust 함수를 Node.js에서 호출할 수 있다.

use neon::prelude::*;

fn process_image(mut cx: FunctionContext) -> JsResult<JsBuffer> {
    let buffer = cx.argument::<JsBuffer>(0)?;
    let width = cx.argument::<JsNumber>(1)?.value(&mut cx) as u32;
    let height = cx.argument::<JsNumber>(2)?.value(&mut cx) as u32;
    
    let data = cx.borrow(&buffer, |data| {
        data.as_slice::<u8>()
    });
    
    // 이미지 처리 로직
    let processed = apply_filter(data, width, height);
    
    let mut result = JsBuffer::new(&mut cx, processed.len() as u32)?;
    cx.borrow_mut(&mut result, |data| {
        data.as_mut_slice().copy_from_slice(&processed);
    });
    
    Ok(result)
}

register_module!(mut cx, {
    cx.export_function("processImage", process_image)
});

Node.js에서는 일반 모듈처럼 사용한다.

const addon = require('../native');

const result = addon.processImage(imageBuffer, width, height);

결과

동일한 작업을 순수 JS로 처리할 때보다 약 3배 빨라졌다. 빌드 설정이 복잡하고 배포 시 플랫폼별 바이너리를 관리해야 하는 단점은 있지만, 성능 크리티컬한 부분에는 충분히 고려할만 하다.

Rust의 소유권 시스템 때문에 러닝커브가 있긴 했지만, 컴파일러가 친절해서 생각보다 빨리 적응할 수 있었다.