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