C 확장 기반 Ruby Gem에서 순수 C 라이브러리로의 RBS 마이그레이션

[EN] From C extension to pure C: Migrating RBS / Alexander Momchilov @amomchilov

작성자
RubyKaigi
발행일
2025년 05월 27일

핵심 요약

  • 1 RBS(Ruby Signature)는 Ruby 코드의 타입을 기술하는 표준 표기법으로, Shopify에서는 Sorbet 타입 체커와 함께 활용됩니다.
  • 2 기존 C 확장 기반의 RBS Gem을 순수 C 라이브러리로 전환하여 병렬 처리, 이식성, 성능 등의 핵심 이점을 확보했습니다.
  • 3 이 과정에서 C 언어의 에러 처리(예외 부재)와 메모리 관리(수동 할당/해제) 등 주요 기술적 도전 과제를 아레나 할당과 같은 방식으로 해결했습니다.

도입

본 발표는 Shopify의 Ruby 개발자 경험 팀 소속인 Alexander Mumilof가 RBS(Ruby Signature) 라이브러리를 C 확장 기반 Ruby Gem에서 순수 C 라이브러리로 마이그레이션한 사례를 공유합니다. RBS는 Ruby 코드 내 변수, 인스턴스 변수, 메서드 반환 값, 매개변수 등의 타입을 명시적으로 기술하는 표준 표기법입니다. 초기에는 별도의 `.rbs` 파일에 정의되었으나, 이후 인라인 RBS 주석 형태로 발전하여 코드와 타입 정의를 한 파일 내에서 관리할 수 있게 되었습니다. Shopify는 RBS의 주된 타입 체커인 Steep 대신 C++로 구현되어 뛰어난 속도와 메모리 효율성을 자랑하는 Sorbet을 사용하고 있습니다. 기존 Sorbet의 SIG 문법은 Ruby DSL(Domain Specific Language)로 구현되어 런타임 의존성을 가지며 다소 번거로운 측면이 있었습니다. 이에 발표 팀은 RBS 주석을 Sorbet에 통합하여 런타임 의존성을 제거하고, 개발 경험을 개선하는 것을 목표로 삼았습니다.

RBS를 순수 C 라이브러리로 전환하는 주요 목표는 RBS의 간결한 문법과 Sorbet의 강력한 성능을 결합하는 것이었습니다. 이 마이그레이션을 통해 얻을 수 있는 핵심적인 이점은 다음과 같습니다. 첫째, Sorbet 런타임 Gem에 대한 의존성을 제거하여 라이브러리 저자가 Sorbet SIG를 사용하더라도 최종 사용자가 Sorbet 런타임에 간접적으로 의존할 필요가 없게 되었습니다. 둘째, Ruby VM의 전역 잠금(Global VM Lock)을 회피함으로써 CPU 병렬 처리를 극대화할 수 있게 되었습니다. 이는 Sorbet이 여러 스레드를 활용하여 파일을 병렬로 파싱하는 능력에 필수적입니다. 셋째, MRI(Matz’s Ruby Interpreter)의 C 확장 API에 대한 의존성을 제거하여 JRuby와 같은 다른 Ruby 런타임에서도 RBS 라이브러리를 활용할 수 있도록 이식성을 높였습니다. 마지막으로, 순수 C 코드를 통해 메모리 레이아웃과 사용량을 더 세밀하게 제어하여 전반적인 성능을 향상시킬 수 있었습니다.

이러한 전환 과정에서는 C 언어의 특성으로 인해 여러 기술적 도전 과제에 직면했습니다. 첫 번째는 Ruby의 강력한 예외 기반 에러 처리 메커니즘이 C에는 없다는 점입니다. 해결책으로 함수 반환 타입을 bool로 변경하여 작업의 성공/실패 여부를 나타내고, 실제 결과 값은 ‘out parameter’를 통해 전달하는 방식을 채택했습니다. 에러 발생 시 false를 반환하고, 호출 스택을 따라 수동으로 에러를 전파해야 하는 번거로움이 있지만, 이는 C에서 에러를 처리하는 일반적인 패턴입니다. 두 번째는 Ruby VM이 제공하는 동적 메모리 할당자와 가비지 컬렉터가 C에는 없으므로, mallocfree를 사용하여 메모리를 수동으로 관리해야 한다는 점입니다. 이는 메모리 누수나 이중 해제와 같은 위험을 내포합니다. 이를 해결하기 위해 아레나 할당(Arena Allocation) 또는 슬랩 할당(Slab Allocation) 방식을 도입했습니다. 이 방식은 유사한 수명을 가진 객체들을 하나의 큰 메모리 블록(아레나)에 할당하고, 해당 아레나의 수명이 종료될 때 한 번에 전체를 해제하는 방식입니다. 이는 개별 free 호출의 복잡성과 성능 오버헤드를 줄여주며, 특히 짧은 수명을 가진 파서 객체 그룹에 매우 효과적입니다. 다만, 할당을 수행하는 함수는 할당자 인스턴스를 매개변수로 받아야 하므로 함수 시그니처가 다소 복잡해질 수 있습니다. 이 외에도 Ruby 표준 라이브러리의 편리한 데이터 구조(Array, Hash 등)를 C에서 직접 구현하거나 검증된 C 컨테이너 라이브러리로 대체해야 했으며, Ruby의 동적 디스패치를 통한 다형성 지원이 C에서는 부족하므로 이에 대한 대안을 마련해야 했습니다.

결론

결론적으로, Ruby Gem에서 순수 C 라이브러리를 추출하는 작업은 이식성, 병렬 처리 능력, 그리고 메모리 효율성 측면에서 상당한 이점을 제공합니다. 이러한 마이그레이션 프로젝트의 성공을 위해서는 개발 초기 단계부터 일관된 에러 처리 패턴과 메모리 생명주기 관리 전략을 수립하는 것이 매우 중요합니다. 특히, 아레나 할당과 같은 메모리 관리 기법은 복잡성을 줄이고 안정성을 높이는 데 기여합니다. 또한, C 개발 시에는 직접 모든 데이터 구조를 구현하기보다는 빠르고 안정성이 검증된 기존 C 라이브러리를 적극적으로 활용하는 것이 권장됩니다. Shopify는 이 프로젝트를 통해 RBS를 자사의 코드베이스와 툴링에 성공적으로 통합하고 있으며, 이는 Ruby 생태계 전반의 타입 검사 및 정적 분석 도구 발전에 긍정적인 영향을 미칠 것으로 기대됩니다.

댓글 0

댓글 작성

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

아직 댓글이 없습니다

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