Rust를 사용하여 Ruby 확장 빌드하기

Guilherme Carreiro, "Building native Ruby extensions in Rust"

작성자
EuRuKo
발행일
2025년 01월 13일

핵심 요약

  • 1 본 강연은 Ruby에서 성능이 중요한 작업을 위해 Rust로 네이티브 확장을 구축하는 여정을 다룹니다.
  • 2 Ruby 및 Rust 생태계를 활용하여 고성능 TOML 파서와 같은 솔루션을 개발하는 방법과 그 과정에서 고려해야 할 사항(오류 처리, 메모리 누수, 배포)을 제시합니다.
  • 3 Magnus와 rb_sys 같은 도구를 사용하여 안전하고 효율적인 Ruby 확장을 만들 수 있음을 강조합니다.

도입

본 강연은 Shopify의 개발자 툴링 팀에서 근무하는 G. Cero가 Ruby 프로그램의 성능 최적화를 위해 Rust로 네이티브 확장을 구축하는 경험과 통찰력을 공유하는 자리입니다. 특히, Liquid 언어 서버 및 테마 렌더링과 같이 성능이 중요한 영역에서 Ruby의 한계를 극복하고, Rust의 강력한 성능을 활용하여 개발 생산성을 향상시키는 방안을 모색합니다. 강연자는 `fast_toml`이라는 실제 사례를 통해 개념을 설명하며, 네이티브 확장이 항상 빠른 것은 아니라는 점과 프로토타이핑의 중요성을 강조합니다.

Ruby에서 네이티브 확장을 구축하는 주된 동기는 성능 향상이지만, 강연자는 순수 Ruby 코드가 특정 상황에서 네이티브 확장보다 빠를 수 있음을 벤치마크를 통해 보여주며 무조건적인 네이티브 확장 도입을 경계합니다. 대신, 실제 성능 병목 지점을 해결하기 위한 대안 중 하나로 Rust 확장을 고려할 것을 제안합니다. Ruby용 Rust 확장을 생성하는 과정은 bundle gem --ext rust 명령어를 통해 시작되며, Cargo.toml, extconf.rb, lib.rs와 같은 핵심 파일들이 이 과정에서 생성됩니다. 특히 extconf.rb는 사용자의 시스템 환경에 맞춰 확장을 컴파일하는 레시피 역할을 하며, lib.rs는 실제 Rust 소스 코드를 담고 있습니다. Ruby VM은 Init_모듈명이라는 명명 규칙을 따르는 함수를 찾아 동적으로 링크된 공유 객체(Shared Object, .bundle, .so, .dll 파일)를 로드하고 실행합니다. 이 과정에서 magnusrb_sys 라이브러리가 중요한 역할을 합니다. magnus는 고수준 바인딩을 제공하여 Rust 데이터 타입을 Ruby 데이터 타입으로 변환하는 작업을 안전하게 처리하며, rb_sys는 더 낮은 수준의 C 바인딩을 제공하지만 일반적으로 개발자가 직접 다룰 필요는 없습니다.

생산 환경에 네이티브 확장을 배포하기 전에는 몇 가지 중요한 고려 사항이 있습니다. 첫째, 오류 처리입니다. Rust의 unwrap과 같은 패닉을 유발하는 코드는 Ruby 프로그램의 실행을 중단시킬 수 있으므로, Result 타입을 사용하여 오류를 명시적으로 처리하고 Ruby 예외로 변환하여 던져야 합니다. 둘째, 메모리 누수 관리입니다. Rust는 메모리 안전성을 표방하지만, unsafe 블록 사용이나 의존성 라이브러리로 인해 메모리 누수가 발생할 수 있습니다. valgrindruby-memcheck와 같은 도구를 사용하여 메모리 누수를 정기적으로 검사하고 관리해야 합니다. 셋째, 배포의 편리성입니다. 사용자가 확장을 설치할 때마다 소스 코드로부터 컴파일하는 것은 시간이 오래 걸리므로, 가장 많이 사용되는 플랫폼에 대해 미리 컴파일된 번들(pre-compiled bundles)을 제공하는 것이 좋습니다. GitHub Actions와 같은 CI/CD 파이프라인을 활용하여 릴리스 시 자동으로 여러 플랫폼용 번들을 생성하고 배포할 수 있습니다.

강연자는 fast_toml이라는 TOML 파서 젬을 예시로 들어, Rust 기반 네이티브 확장이 기존 Ruby 기반 파서보다 훨씬 빠른 성능을 달성할 수 있음을 벤치마크를 통해 입증했습니다. 다만, 파싱 성능은 뛰어나지만 TOML 데이터를 다시 문자열로 생성하는 과정에서는 기존 Ruby 옵션보다 빠르지 않다는 한계점도 명확히 밝혔습니다. 이는 특정 작업에 대한 최적화를 의미하며, 모든 기능이 네이티브 확장으로 대체될 필요는 없음을 시사합니다.

결론

결론적으로, Ruby에서 성능이 중요한 작업을 수행할 때 Rust로 네이티브 확장을 구축하는 것은 매우 효과적인 전략이 될 수 있습니다. 이는 Ruby와 Rust 두 생태계의 강점을 모두 활용하여 계산 집약적인 문제를 해결하는 실용적인 방법을 제공합니다. 강연자는 개발자들이 새로운 기능을 처음부터 구현하기보다는, 기존의 Ruby 젬이나 Rust 크레이트를 적극적으로 재사용하여 효율성을 높일 것을 권장합니다. 오류 처리, 메모리 관리, 그리고 배포 전략에 대한 신중한 접근은 네이티브 확장을 성공적으로 프로덕션에 적용하는 데 필수적입니다. `fast_toml` 프로젝트는 이러한 접근 방식의 유효성을 보여주는 훌륭한 개념 증명 사례이며, 관심 있는 개발자들에게 오픈소스로 제공되어 추가적인 실험과 기여를 독려합니다.

댓글 0

댓글 작성

0/1000
정중하고 건설적인 댓글을 작성해 주세요.

아직 댓글이 없습니다

첫 번째 댓글을 작성해보세요!