Rust로 Node.js Native Addon 작성하기

문제 상황

사용자 업로드 이미지를 여러 크기로 리사이징하는 API가 느렸다. Sharp 라이브러리를 사용했지만, 동시에 100개 이상의 요청이 들어오면 응답 시간이 3초를 넘었다.

Rust 선택 이유

  • C/C++보다 메모리 안전성 보장
  • neon 또는 napi-rs로 Node.js 바인딩 가능
  • 이미지 처리 라이브러리(image crate) 성능 우수

구현

napi-rs를 사용해 Rust 함수를 Node.js에서 호출할 수 있도록 했다.

use image::imageops::FilterType;
use napi::bindgen_prelude::*;
use napi_derive::napi;

#[napi]
pub fn resize_image(input: Buffer, width: u32, height: u32) -> Result<Buffer> {
  let img = image::load_from_memory(&input)
    .map_err(|e| Error::from_reason(e.to_string()))?;
  
  let resized = img.resize_exact(width, height, FilterType::Lanczos3);
  let mut output = Vec::new();
  resized.write_to(&mut std::io::Cursor::new(&mut output), image::ImageFormat::Jpeg)
    .map_err(|e| Error::from_reason(e.to_string()))?;
  
  Ok(output.into())
}

빌드 후 Node.js에서 사용:

const { resizeImage } = require('./index.node');

app.post('/resize', async (req, res) => {
  const buffer = await resizeImage(req.file.buffer, 800, 600);
  res.send(buffer);
});

결과

  • 평균 응답 시간: 3.2초 → 0.8초
  • CPU 사용률 40% 감소
  • 메모리 누수 없음

배운 점

Rust의 학습 곡선은 가파르지만, 성능이 중요한 부분에 선택적으로 도입하면 효과적이다. 전체를 Rust로 재작성할 필요는 없고, 병목 지점만 개선해도 충분했다.