GoGem Wrapper를 이용한 Go 언어로 Ruby 네이티브 확장 개발

[JA] Road to Go gem / Go Sueyoshi @sue445

3줄 요약

  • GoGem Wrapper는 Go 언어로 Ruby 네이티브 확장을 쉽게 개발할 수 있도록 돕는 라이브러리입니다.
  • 이 라이브러리는 Go의 강력한 동시성 모델인 고루틴을 활용하여 루비의 병렬 처리 성능을 획기적으로 개선합니다.
  • Go-Ruby 간의 복잡한 바인딩 및 빌드 과정을 자동화하여 개발 편의성을 크게 향상시켰습니다.

본 발표는 Ruby 네이티브 확장을 Go 언어로 개발하는 과정과 이를 위한 새로운 라이브러리인 GoGem Wrapper에 대해 다룹니다. 10년 전 RubyConf에서 소개되었던 'Ruby Meets Go' 발표는 Go의 Cgo 기능을 활용하여 Ruby 네이티브 확장을 만들 수 있음을 보여주었지만, 당시의 기술로는 실제 구현에 많은 어려움이 있었습니다. GoGem Wrapper는 이러한 문제점들을 해결하고, Go 언어의 강점을 Ruby 생태계에 효과적으로 통합하기 위해 개발되었습니다.

GoGem Wrapper는 Go 모듈과 Ruby 젬으로 구성된 라이브러리로, Go 언어로 Ruby 젬을 쉽게 생성할 수 있도록 다양한 패치와 기능을 통합합니다. 과거에는 Go로 젬을 만들기 위해 수많은 수동 패치가 필요했지만, GoGem Wrapper는 이 과정을 자동화하여 bundle gem 명령어로 스켈레톤을 생성한 후 patch4GoGem 스크립트 실행만으로 Go 젬 개발 환경을 구축할 수 있게 합니다. 특히 Ruby 헤더 파일에 정의된 약 1,100개의 함수에 대한 Go 바인딩을 자동 생성하여 개발자가 C-Ruby API를 직접 다루는 복잡성을 줄였습니다. 이는 약 9,200라인의 Go 소스 코드를 생성하는 방대한 작업입니다.

GoGem Wrapper 개발 과정에서는 여러 난관에 봉착했습니다. 첫째, Go 언어의 버전 호환성 문제, 특히 Go 1.7 이후 Go 문자열이 널 종료(null-terminated) 방식이 아니어서 C 언어와 상호 운용 시 세그멘테이션 폴트가 발생하는 문제를 해결해야 했습니다. 둘째, C 언어의 복잡한 포인터 및 타입 선언을 Go 타입으로 정확하게 변환하는 자동 바인딩 생성기가 필요했습니다. 이는 C 타입만으로는 Go 타입을 유일하게 결정할 수 없으며, 각 경우에 따라 개별적인 대응이 필요했기 때문입니다. 셋째, Ruby의 마이너 버전별로 다른 헤더 파일 정의에 대응하기 위해 Go의 빌드 제약 조건(build constraints)을 활용하여 버전별 바인딩을 동적으로 전환하는 메커니즘을 구현했습니다. 넷째, pkg.go.dev와 같은 공식 Go 패키지 문서 사이트에서 사용자 정의 빌드 제약 조건이 있는 경우 문서가 올바르게 표시되지 않는 문제를 해결하기 위한 방안을 모색했습니다. 마지막으로, Go의 가비지 컬렉터가 Ruby 객체의 참조를 인식하지 못하여 발생하는 문제에 대응하기 위해 Ruby의 참조 카운터를 Go로 전달하여 Go 내에서 Ruby 객체가 더 이상 참조되지 않을 때 자동으로 해제되도록 처리했습니다.

GoGem Wrapper의 가장 큰 장점은 Go의 고루틴(Goroutine)을 활용하여 Ruby 애플리케이션의 동시성 및 병렬 처리 성능을 크게 향상시킬 수 있다는 점입니다. 벤치마크 결과에 따르면 고루틴은 특정 병렬 처리 작업에서 Ruby의 Ractor보다 약 30배, Fiber보다 약 20배 빠른 성능을 보여주었습니다. 이는 Ruby가 취약한 병렬 처리 분야에서 Go의 강점을 활용할 수 있음을 의미합니다. 실제 사례로, 여러 HTTP 요청을 고루틴을 사용하여 병렬로 실행하는 Funnel HTTP 젬이 소개되었습니다.

물론 GoGem Wrapper에도 몇 가지 단점이 존재합니다. 여전히 저수준(low-level) 라이브러리이므로 C-Ruby와 Go 언어에 대한 이해가 모두 필요합니다. 또한, Cgo의 가변 인자 함수 호출 문제, 고루틴 내에서 C-Ruby 함수 호출 시 세그멘테이션 폴트 발생 가능성, 그리고 GLib 라이브러리 의존성으로 인해 Alpine Linux나 Windows와 같은 특정 환경에서의 제약이 있습니다.

결론적으로, GoGem Wrapper는 Go 언어로 Ruby 네이티브 확장을 개발하는 과정을 크게 단순화하고, Go의 강력한 동시성 모델인 고루틴을 Ruby 애플리케이션에 도입하여 성능을 향상시킬 수 있는 획기적인 솔루션입니다. 비록 몇 가지 기술적 난제와 제약이 남아있지만, GoGem Wrapper는 Ruby 개발자들이 Go의 잠재력을 활용하여 Ruby 애플리케이션의 한계를 뛰어넘을 수 있는 새로운 가능성을 제시합니다. 이는 Ruby와 Go의 협업을 통해 더욱 강력하고 효율적인 시스템을 구축할 수 있음을 입증하는 사례입니다.