Ruby 프로파일러 Stackprof의 작동 방식: 현미경으로 들여다보기

[Euruko 2025] “How a Ruby profiler works: Stackprof under a microscope” – Ivo Anjo (Datadog, UK)

작성자
jeff
발행일
2025년 11월 10일

핵심 요약

  • 1 Stackprof은 낮은 오버헤드를 가진 Ruby 프로파일러로, 샘플링 기법을 통해 CPU, Wall Time, 객체 할당 등을 효율적으로 측정하며 다른 프로파일러 개발에 영감을 주었습니다.
  • 2 프로파일러는 샘플링 시점 결정, 샘플 수집, 저장, 결과 포맷팅의 네 가지 핵심 구성 요소로 이루어지며, Ruby의 내부 API(RB_PROFILE_FRAMES, postponed jobs) 및 운영체제 신호를 활용합니다.
  • 3 Flame Graph는 Stackprof의 주요 출력 형식 중 하나로, 애플리케이션의 CPU 사용량 및 병목 지점을 시각적으로 파악하여 성능 최적화에 기여하는 강력한 도구입니다.

도입

본 발표는 Ruby 프로파일러인 Stackprof의 작동 원리를 심층적으로 분석합니다. 발표자인 Ivaju는 포르투갈 출신 Ruby 개발자로, 11년간 Ruby와 함께하며 현재 Datadog의 Ruby 프로파일러 엔지니어로 재직 중입니다. 그는 Stackprof이 낮은 오버헤드로 Ruby 애플리케이션의 성능을 측정하고 시각화하는 방식에 깊은 관심을 가지고 있으며, Stackprof이 Datadog Ruby 프로파일러 개발에 큰 영감을 주었음을 밝힙니다. 이 발표는 Stackprof의 내부 구조를 상세히 설명함으로써 프로파일러가 어떻게 작동하는지에 대한 이해를 돕고, 개발자들이 애플리케이션 성능 문제를 해결하는 데 필요한 통찰력을 제공하고자 합니다.

Stackprof 개요

Stackprof은 2013년 Aman Carmani가 개발하고 현재 Aaron Patterson이 유지보수하는 Ruby 프로파일러 Gem입니다. 2,000라인 미만의 C 코드로 구현되어 낮은 오버헤드를 자랑하며, CPU 시간, Wall 시간, 객체 할당 등 다양한 지표를 프로파일링할 수 있습니다. 특히 Flame Graph를 포함한 여러 출력 형식을 지원하며, DataDog Ruby 프로파일러와 Rack Mini Profiler 등 다른 도구들에게 영감을 주었습니다.

샘플링 (Sampling) 기법

Stackprof과 같은 낮은 오버헤드 프로파일러의 핵심은 ‘샘플링’입니다. 애플리케이션에서 발생하는 모든 이벤트를 상세히 기록하는 대신, 주기적으로 애플리케이션의 상태를 확인하여 오버헤드를 최소화합니다. 예를 들어, 모든 메서드 호출을 세는 대신 10ms 또는 1ms마다 Ruby가 무엇을 하는지 확인하여 대략적인 CPU 사용 비율을 파악합니다.

프로파일러의 4가지 주요 구성 요소

Stackprof은 다음과 같은 네 가지 주요 구성 요소로 작동합니다.

1. 샘플링 시점 결정 (When to Sample)

  • 객체 할당 (Object Allocations): Ruby의 TracePoint 클래스를 활용합니다. 특히 C 코드에서만 접근 가능한 Ruby internal event new object 이벤트를 사용하여 새로운 객체가 생성될 때마다 특정 C 함수를 호출하도록 설정합니다.

  • CPU 및 Wall Time: Unix/Linux 운영체제의 interval timer API를 사용합니다. setitimer 함수를 통해 운영체제에 주기적으로 신호를 발생시키도록 요청하고, sigaction 함수로 해당 신호(예: SIGALRM for Wall time, SIGPROF for CPU time)를 처리할 함수를 등록합니다.

    • CPU Time vs. Wall Time: CPU 시간은 애플리케이션이 실제 CPU를 사용하여 코드를 실행하는 시간이며, Wall 시간은 실제 세계에서 경과하는 시간입니다. Sleep이나 데이터베이스 호출과 같이 CPU를 사용하지 않는 대기 시간은 Wall Time에는 포함되지만 CPU Time에는 포함되지 않습니다. 두 지표 모두 애플리케이션 최적화 및 지연 시간 분석에 중요합니다.

2. 샘플 수집 (Taking a Sample)

신호 핸들러(sigprof_signal_handler)나 객체 할당 이벤트 발생 시, buffer_sample 함수가 호출됩니다. 이 함수는 현재 타임스탬프를 기록하고, RB_PROFILE_FRAMES API를 사용하여 현재 스레드의 백트레이스(호출 스택)를 가져옵니다. 가비지 컬렉션(GC) 중일 경우 stackprof_job_record_gc 함수를 통해 GC 관련 정보를 기록합니다.

3. 샘플 저장 (Storing a Sample)

샘플 수집 후, postponed jobs API를 통해 안전한 시점에 샘플을 저장합니다. 이 API는 운영체제 신호 핸들러 내에서 메모리 할당이나 대부분의 Ruby VM API 호출이 안전하지 않기 때문에 필요합니다. Ruby VM이 ‘안전 지점(safe point)’에 도달했을 때(예: 전역 VM 락을 확보하고 GC 중이 아닐 때) 저장 작업을 수행하도록 지연시킵니다. Stackprof은 두 가지 방식으로 데이터를 저장합니다.

  • Raw Samples: 백트레이스와 해당 샘플이 발생한 타임스탬프를 별도의 배열에 저장합니다. 중복되는 백트레이스가 연속적으로 나타나면 카운트를 증가시킵니다.

  • Aggregate Samples: 타임스탬프 없이 백트레이스만 압축하여 저장합니다. 이는 메모리 사용량을 절감하고 반복을 줄여 효율성을 높입니다.

4. 결과 포맷팅 (Formatting the Results)

저장된 샘플 데이터는 다양한 형식으로 변환될 수 있습니다. Stackprof은 특히 stack collapse 형식을 지원하는데, 이는 각 라인에 백트레이스와 해당 백트레이스가 관찰된 횟수를 기록하는 텍스트 파일입니다. 이 파일을 speedscope.app과 같은 웹사이트에 업로드하면 Flame Graph로 시각화하여 애플리케이션의 성능 병목 지점을 직관적으로 파악할 수 있습니다.

  • Flame Graph 해석: 좌우 축은 시간 순서가 아닌 정렬된 순서를 나타내며, 상하 축은 메서드 호출 스택을 의미합니다. 각 블록의 너비는 해당 메서드가 프로파일러에 의해 관찰된 빈도(또는 시간)를 나타내므로, 넓은 블록이 성능 최적화의 잠재적 대상임을 시각적으로 보여줍니다.

결론

Stackprof은 단 2,000라인 미만의 코드로 구현된 매우 효율적이고 유용한 Ruby 프로파일러입니다. 이 도구는 낮은 오버헤드 프로파일링의 핵심인 샘플링 기법과 Unix/Linux 운영체제 신호, 그리고 Ruby VM의 `RB_PROFILE_FRAMES` 및 `postponed jobs` API를 효과적으로 활용하여 애플리케이션의 다양한 측면(CPU, Wall Time, 객체 할당)을 분석할 수 있도록 합니다. Stackprof의 내부 작동 방식을 이해하는 것은 프로파일러가 어떻게 애플리케이션 성능 데이터를 수집하고 시각화하는지에 대한 깊은 통찰력을 제공하며, 이는 Ruby 개발자들이 자신의 애플리케이션에서 발생하는 버그와 성능 문제를 진단하고 해결하는 데 큰 도움이 될 것입니다. 발표자는 더 많은 개발자들이 프로파일러를 실험하고 구축해보기를 독려하며, 이는 Ruby 생태계의 발전에 기여할 것이라고 강조합니다.

댓글 0

로그인이 필요합니다

댓글을 작성하거나 대화에 참여하려면 로그인이 필요합니다.

로그인 하러 가기

아직 댓글이 없습니다

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