Ruby 애플리케이션, 특히 IO 바운드 애플리케이션에서 성능 문제를 분석할 때, 특정 IO 작업에 소요된 정확한 시간을 측정하는 것은 생각보다 복잡합니다. 전통적인 시간 측정 방식은 데이터베이스 응답 시간 외에도 Ruby 스레드가 GVL(Global Virtual Lock)을 다시 얻거나, GC(Garbage Collection)가 실행되거나, 심지어 운영체제 스케줄러가 프로세스를 재개하는 데 걸린 시간까지 포함할 수 있습니다. 이로 인해 실제 IO 대기 시간과 다른 요인으로 인한 대기 시간을 구별하기 어렵다는 문제가 발생합니다. 본 글에서는 이러한 문제를 해결하기 위해 Ruby에 도입된 새로운 계측 API와 그 활용 방안에 대해 심층적으로 살펴봅니다.
가장 먼저 다루는 것은 GC.total_time
API입니다. 데이터베이스 쿼리나 다른 IO 작업은 종종 많은 객체 할당을 유발하며, 이는 GC 실행으로 이어질 수 있습니다. GC에 소요된 시간을 측정하는 것은 코드 블록 내에서 발생하는 ‘멈춤’ 현상의 원인을 파악하는 데 중요합니다. GC.total_time
은 GC에 소요된 전체 시간을 나노초 단위로 제공하는 단조 증가 카운터이며, 이를 활용하여 특정 코드 블록 실행 중 GC에 소요된 시간을 정확히 측정할 수 있습니다. Rails 7.2부터는 이 측정값이 Rails 계측 API에 통합되어 모든 ActiveSupport::Notifications 이벤트에 gc_time
이 포함되며, 요청 로그에도 전체 GC 시간이 기록됩니다. 다만, 다중 스레드 환경에서는 다른 스레드로 인한 GC 시간을 단순히 IO 시간에서 제외하기 어렵다는 점에 유의해야 합니다.
다음으로 중요한 요소는 GVL 경합입니다. 너무 많은 스레드를 사용하도록 애플리케이션을 설정하면, IO 완료 후 스레드가 재개되는 데 상당한 지연이 발생할 수 있습니다. Ruby 3.2에서는 GVL 계측을 위한 새로운 C API가 도입되었습니다. 이 API는 저수준이지만, gvltools
, gvl_timing
, gvl-tracing
과 같은 gem을 통해 활용할 수 있습니다. gvltools
를 사용하면 실제 IO 시간과 GVL 대기 시간을 명확히 분리하여 측정하는 것이 가능합니다. 예제 코드를 통해 CPU 집약적인 백그라운드 스레드가 있을 때 GVL 대기 시간이 어떻게 크게 증가하는지 확인할 수 있습니다. 이 API는 매우 유용하지만, Ruby 스레드 스케줄러에 일부 오버헤드를 추가할 수 있다는 단점이 있으며, 이로 인해 Rails 기본값으로 통합하기에는 아직 조심스러운 부분이 있습니다. 하지만 최근 APM 서비스에서 이 계측 API를 활용하기 위한 미들웨어가 오픈소스로 공개되는 등 점차 활용도가 높아질 것으로 기대됩니다.
마지막으로, 운영체제 스케줄러 역시 IO 작업 시간을 실제보다 길게 보이게 만드는 요인이 될 수 있습니다. 전용 하드웨어에서 코어당 하나의 Ruby 프로세스만 실행하는 경우가 아니라면, 운영체제가 IO 블록킹이 끝난 프로세스를 즉시 재개하지 못하는 상황이 발생할 수 있습니다. Linux의 /proc/<pid>/schedstat
파일의 두 번째 값(runqueue 대기 시간)을 통해 이를 간접적으로 측정할 수 있지만, 모든 IO 호출마다 이 값을 읽는 것은 과도한 오버헤드를 유발하므로 애플리케이션 모니터링에 직접 통합하기는 어렵습니다. 대신, 컨테이너당 너무 많은 프로세스가 실행되고 있음을 나타내는 전역적인 지표로 활용하는 것이 일반적입니다.
결론적으로, 개별 IO 작업에 소요된 정확한 시간을 완벽하게 측정하는 것은 매우 어렵습니다. 하지만 Ruby에 새로 추가된 `GC.total_time` 및 GVL 계측 API와 같은 도구들은 애플리케이션이 경험하는 '스레드 멈춤(thread stalling)' 현상의 원인과 정도를 더 잘 파악하는 데 큰 도움을 줍니다. 이러한 새로운 지표들을 통해 개발자는 애플리케이션의 성능 병목 지점을 더욱 정확하게 분석하고 개선할 수 있습니다. 향후 다양한 APM 서비스에서 이러한 새로운 계측 API를 통합하여 제공한다면, Ruby 애플리케이션의 성능 모니터링 및 최적화가 더욱 용이해질 것으로 예상됩니다.