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로 재작성할 필요는 없고, 병목 지점만 선택적으로 적용하는 게 현실적이다.