Rust의 소유권 시스템으로 메모리 안전성 확보하기

문제 상황

대용량 이미지 리사이징 API가 트래픽이 몰릴 때마다 메모리 사용량이 급증하는 문제가 있었다. Node.js의 싱글 스레드 특성상 동시 처리에 한계가 있었고, worker_threads를 사용해도 GC 오버헤드가 컸다.

Rust로의 전환

Rust로 별도 마이크로서비스를 만들어 이미지 처리 로직을 분리했다. image 크레이트와 actix-web을 사용했다.

use image::imageops::FilterType;

pub fn resize_image(input: &[u8], width: u32, height: u32) -> Result<Vec<u8>, ImageError> {
    let img = image::load_from_memory(input)?;
    let resized = img.resize(width, height, FilterType::Lanczos3);
    
    let mut output = Vec::new();
    resized.write_to(&mut output, image::ImageFormat::Jpeg)?;
    Ok(output)
}

소유권의 장점

처음엔 borrow checker와 씨름했지만, 컴파일 타임에 메모리 관련 버그가 모두 잡혔다. 특히 &[u8]로 입력을 받고 Vec<u8>로 반환하는 구조에서 명확한 소유권 이전이 일어나 런타임 에러가 없었다.

성과

  • 응답 시간 70% 감소 (평균 450ms → 135ms)
  • 메모리 사용량 안정화 (피크 타임에도 일정)
  • 동시 요청 처리량 3배 증가

Rust의 러닝 커브는 가팔랐지만, 성능 크리티컬한 부분에선 충분히 투자할 가치가 있었다. 전체를 Rust로 재작성할 필요는 없고, 병목 지점만 선택적으로 적용하는 게 현실적이다.