Ruby의 네임스페이스 기능 개발 현황 및 도전 과제

[JA] State of Namespace / Satoshi Tagomori @tagomoris

3줄 요약

  • Ruby의 네임스페이스는 애플리케이션과 라이브러리를 격리된 공간에 로드하여 버전 충돌 문제를 해결하는 기능입니다.
  • 지난 1년간 기능 구현보다 디버깅에 집중했으며, 오토로드, 상수 처리, 확장 라이브러리 로딩 등 기존 Ruby 내부 구조와의 복잡한 상호작용에서 다양한 문제에 직면했습니다.
  • 현재 일부 잔여 문제가 있지만, 개발 브랜치를 메인라인에 병합하여 Ruby 3.5 또는 4.0에 포함시키는 것을 목표로 하고 있습니다.

본 발표는 타그모리스(Tagomoris)가 Ruby의 네임스페이스 기능 개발 과정에서 겪었던 도전과 해결책을 공유합니다. 네임스페이스는 Ruby 애플리케이션 및 라이브러리를 격리된 공간에 로드하여, 동일한 라이브러리의 여러 버전을 동시에 사용하거나 전역 상태를 오염시키는 문제를 해결하기 위해 제안된 기능입니다. 지난 RubyKaigi 이후 네임스페이스 기능 자체의 큰 변화는 없었으나, 기능의 안정화와 견고한 동작을 위한 지난 1년간의 치열한 디버깅 과정이 주된 내용입니다.

현재 Ruby는 단일 프로세스 내에서 모든 클래스와 모듈을 공유하는 단일 공간 구조를 가집니다. 이로 인해 동일 라이브러리의 다른 버전을 로드할 때 정의 충돌이 발생할 수 있습니다. 네임스페이스는 프로세스 내에 격리된 공간을 생성하여, 각 공간 내에서 로드된 코드의 변경 사항이 해당 네임스페이스 내에만 영향을 미치도록 합니다. 이를 통해 외부 또는 다른 네임스페이스에서는 동일 라이브러리의 다른 버전을 로드할 수 있게 됩니다. 네임스페이스 내부의 메서드가 호출될 경우, 해당 네임스페이스 내에 정의된 내용을 참조하여 동작합니다.

지난 1년간의 개발은 주로 안정성 확보에 초점을 맞췄습니다. 작년 RubyKaigi 2024 당시 데모 코드가 종종 세그멘테이션 오류(segfault)를 일으키고 make test도 중간에 멈추는 등 불안정한 상태였습니다. 이후 지속적인 디버깅을 통해 make test는 10월에, make check는 올해 1월에 통과했습니다. 그러나 make exam은 여전히 일부 실패하는 상황입니다.

개발 과정에서 여러 복잡한 문제들이 드러났습니다. 첫째, Ruby 클래스 정의의 내부 구조(RClass와 ClassEXT)와 네임스페이스 간의 상호작용이 예상보다 복잡했습니다. 네임스페이스는 클래스 정의를 네임스페이스별로 가질 수 있도록 ClassEXT를 얕은 복사(shallow copy)하여 사용하려 했으나, 이는 Ruby::Version 상수와 같은 경우에서 심각한 문제를 야기했습니다. 특정 네임스페이스에서 상수를 삭제하면 원본 ClassEXT의 상수 값이 해제되어 유효하지 않은 메모리 참조로 인한 세그멘테이션 오류가 발생했습니다. 해결책으로 상수 테이블을 깊은 복사(deep copy)하도록 변경했습니다.

둘째, 오토로드(Autoload) 기능과의 충돌이 있었습니다. 루트 네임스페이스에서 정의된 오토로드 항목이 다른 네임스페이스에서 먼저 참조되어 정의가 소비되면, 루트 네임스페이스에서는 해당 오토로드 항목을 더 이상 사용할 수 없어 NameError가 발생했습니다. 이를 해결하기 위해 오토로드 정의를 깊은 복사하여 각 네임스페이스가 독립적인 오토로드 정의를 가질 수 있도록 하는 매우 복잡한 로직이 추가되었습니다.

셋째, 확장 라이브러리(Extension Library) 로딩 시 killed 9 (macOS) 또는 세그멘테이션 오류 (Linux)가 발생했습니다. 이는 dlopen 시스템 호출이 동일한 파일을 한 번만 열기 때문에, 각 네임스페이스에 확장 라이브러리를 로드하기 위해 임시 파일로 복사할 때 파일명 생성 로직의 충돌로 인해 발생했습니다. 즉, CGI/EscapeERB/Escape가 동일한 임시 파일명으로 복사되어 기존 로드된 라이브러리를 덮어쓰면서 미정의 동작(undefined behavior)이 발생한 것입니다. 이 문제는 확장 라이브러리의 전체 경로를 사용하여 임시 파일명을 고유하게 생성하도록 수정하여 해결했습니다.

이 외에도 네임스페이스 개발 과정에서 rb_ensure와 컨티뉴에이션(continuation)의 상호작용 버그, CME (Callable Method Entry) 소유자 문제, 그리고 Prism 파서 사용 시 -O0 최적화 옵션에서 발생하는 시스템 스택 오류 등 여러 Ruby 인터프리터(MRI) 내부 버그를 발견하고 보고하는 성과도 있었습니다. 루비 메인라인의 GC 구조 변경 등 잦은 내부 수정으로 인해 장기간 유지된 개발 브랜치를 리베이스하는 데에도 큰 어려움이 따랐습니다.

네임스페이스 기능은 여전히 해결해야 할 과제들을 안고 있습니다. 현재의 스택 기반 네임스페이스 관리 방식으로는 일부 코너 케이스에서 문제가 발생할 수 있어 컨트롤 프레임 기반으로의 전환을 고려하고 있습니다. 또한, GC 시 네임스페이스별 `ClassEXT` 잔여 문제 해결 및 임시 `soo` 파일 처리 등 GC 관련 개선이 필요합니다. 발표자는 장기간 유지된 개발 브랜치를 메인라인에 조속히 병합하는 것이 중요하다고 강조하며, 환경 변수를 통해 활성화되는 형태로라도 Ruby 3.5 또는 Ruby 4.0에 네임스페이스 기능이 포함되어 사용자들이 이 유용한 기능을 활용할 수 있기를 희망하며 발표를 마쳤습니다.