SDB: GVL 없이 효율적인 Ruby 스택 스캐닝

[EN] SDB: Efficient Ruby Stack Scanning Without the GVL / Mike Yang @yfractal

3줄 요약

  • SDB는 기존 Ruby 스택 프로파일러의 한계를 극복하기 위해 GVL 없이 스택을 스캔하는 새로운 방식의 프로파일러입니다.
  • 1ms의 높은 샘플링 속도와 3% 미만의 낮은 CPU 사용률로 운영 환경에 최소한의 영향을 미치며 상세한 성능 데이터를 제공합니다.
  • 이를 통해 기존 도구의 사각지대를 해소하고, 상시 활성화 가능한 기본 관측 솔루션으로서의 잠재력을 가집니다.

Ruby 애플리케이션의 성능 문제를 진단하는 데 있어 기존 도구들은 한계점을 가지고 있습니다. `instrumentation` 방식은 특정 함수 주변에 코드를 삽입하여 지연 시간을 측정하지만, 모든 함수에 적용하기 어렵고 오버헤드가 발생하며, 측정되지 않는 '사각지대'가 존재합니다. 반면, 기존 스택 프로파일러들은 주로 평균 지연 시간 측정에 초점을 맞추고 있으며, 높은 CPU 사용률과 Ruby의 Global Interpreter Lock (GVL)을 점유하여 애플리케이션 속도를 저하시키는 문제가 있습니다. 이러한 문제점들로 인해 단일하고 효율적인 성능 분석 도구의 필요성이 제기되었으며, 이 발표에서는 GVL 없이 효율적인 Ruby 스택 스캐닝을 가능하게 하는 새로운 스택 프로파일러인 SDB(Stack Debugger)를 소개합니다. SDB는 기존 도구들의 단점을 보완하며, 고해상도 샘플링과 낮은 성능 오버헤드를 목표로 설계되었습니다.

SDB는 기존 프로파일러가 직면한 문제를 해결하기 위해 세 가지 핵심 목표를 설정했습니다. 첫째, 샘플링 간격을 기존 10밀리초에서 1밀리초로 줄여 더 세밀한 데이터를 확보합니다. 둘째, CPU 사용률을 3~5% 미만으로 유지하여 운영 환경에 미치는 영향을 최소화합니다. 셋째, 스택 스캔 시 GVL을 사용하지 않아 애플리케이션의 실행을 방해하지 않습니다.

SDB의 아키텍처는 크게 ‘시그널 트리거(Signal Trigger)’와 ‘시그널 핸들러(Signal Handler)’로 구성됩니다. 시그널 트리거는 정기적으로 애플리케이션 스레드에 시그널을 보내는 역할을 하며, 이 과정에서 GVL을 해제하여 오버헤드를 줄입니다. 시그널 핸들러는 시그널을 수신한 후 스택을 스캔하고 데이터를 처리합니다. 핸들러는 다시 ‘애널라이저(Analyzer)’와 ‘심볼라이저(Symbolizer)’로 나뉩니다. 애널라이저는 수집된 스택 정보를 집계하고, 함수별 지연 시간 등을 추론하며, 데이터를 불꽃 그래프(flame graph)와 같은 형태로 변환합니다. SDB는 프로덕션 환경에 미치는 영향을 최소화하기 위해 ‘지연 및 오프라인 분석(lazy and offline analyze)’ 방식을 채택하여, 필요한 경우에만 데이터를 분석하고 이를 다른 머신에서 수행할 수 있도록 합니다. 심볼라이저는 메모리 주소를 사람이 읽을 수 있는 함수 이름 및 상세 정보로 변환하는 역할을 합니다. Ruby 애플리케이션의 메서드 수가 제한적이라는 점을 활용하여 번역 결과를 캐싱함으로써 효율성을 높입니다.

SDB의 핵심 혁신은 GVL 없이 스택을 스캔하는 능력에 있습니다. GVL은 Ruby 객체를 보호하고 여러 명령어를 하나의 원자적 단위로 묶어 스레드 안전성을 보장하는 역할을 합니다. 하지만 SDB 개발자는 단일 명령어가 원자성을 보장하는 경우(예: 64비트 메모리 읽기/쓰기) GVL이 불필요하다는 점에 주목했습니다. SDB의 스택 스캐너는 Ruby 메서드의 내부 표현인 ISEQ 필드에만 의존하며, 이 필드는 64비트 원자적 메모리 읽기가 가능합니다. 또한, SDB는 활성 스레드만 스캔하도록 설계되었으며, Ruby의 가비지 컬렉션(GC) 및 메모리 압축(memory compaction)과도 안전하게 동작합니다. 이는 SDB가 실행 컨텍스트(execution context)와 스택 같은 고정된 객체에만 의존하기 때문입니다. 비록 데이터 경합(data races)과 같은 잠재적인 스레드 안전성 문제가 있을 수 있음을 인정하지만, 스택 프로파일러의 목적이 ‘엄격한 정확성’보다는 ‘실용적인 유용성’에 있음을 강조합니다. 즉, 매우 짧은 시간 동안 발생하는 함수에서만 잘못된 결과가 발생할 수 있으므로, 고지연 함수 감지라는 주된 목적에는 영향을 미치지 않는다는 논리입니다.

SDB의 성능은 여러 평가를 통해 입증되었습니다. AWS 머신에서의 테스트 결과, GVL 기반 솔루션은 샘플링 간격이 줄어들수록 실행 시간이 급격히 증가했지만, SDB는 1밀리초 샘플링에서도 실행 시간에 거의 영향을 미치지 않았습니다. 실제 Ruby on Rails 애플리케이션인 중국의 유명 루비 포럼 ‘Homeland’에서의 평가에서는 SDB가 평균 지연 시간을 0.5%만 증가시키고 CPU 사용률을 3% 미만으로 유지하는 놀라운 결과를 보였습니다. 이는 RBspy(높은 CPU 사용률)나 Walrus 1.0(10% 지연 시간 증가)과 같은 다른 프로파일러에 비해 월등히 낮은 오버헤드입니다. 이러한 결과는 SDB가 ‘항상 활성화’될 수 있는 기본 관측 솔루션으로서의 가능성을 보여줍니다. 또한, SDB는 단일 요청에 대해서도 매우 상세한 정보를 수집할 수 있음을 입증했습니다.

SDB의 개발은 스택 스캐너와 심볼라이저 코드가 1,000줄 미만으로 작지만, Ruby VM의 GVL, GC, ISEQ, 저수준 동시성 등 내부 동작에 대한 깊은 이해를 필요로 하는 도전적인 작업이었습니다. 개발자는 이러한 추가적인 노력에 대한 가치는 개인마다 다를 수 있지만, Ruby VM이라는 복잡한 시스템을 배우고 디버깅하는 과정 자체가 매우 흥미롭고 기억에 남는 경험이었다고 회고합니다.

현재 SDB는 실험 단계에 있으며, 특정 Ruby 버전만 지원합니다. 향후 더 많은 Ruby 버전을 지원하고, eBPF 의존성을 제거한 심볼라이저 재작성, 그리고 Ruby 내부 이벤트 통합을 통해 더 쉬운 문제 감지를 목표로 하고 있습니다. SDB는 가능한 한 많은 정보를 수집하면서도 리소스 사용을 최소화하여 실제 산업 프로젝트에서 활용될 수 있는 실용적인 균형을 찾고자 합니다.

결론적으로, SDB는 GVL을 해제하고 스택을 스캔하는 혁신적인 접근 방식을 통해 Ruby 성능 프로파일링의 새로운 지평을 열었습니다. 높은 샘플링 속도와 낮은 성능 오버헤드를 제공함으로써, SDB는 기존 `instrumentation` 방식이나 GVL 기반 스택 프로파일러의 한계를 극복할 수 있습니다. 이러한 특성 덕분에 SDB는 Ruby 애플리케이션을 위한 상시 활성화 가능한 기본 관측 솔루션으로서 막대한 잠재력을 가지고 있습니다. SDB는 성능 디버깅 노력을 줄이고, 더 많은 정보를 수집하여 근본 원인 분석을 단순화하는 데 기여할 것입니다.