원문 캡처 · datadoghq.com
원문 캡처 · datadoghq.com

Datadog은 asyncio 작업 사이의 관계를 재구성해 애플리케이션 실행 경로를 보여주는 Python 프로파일러를 개선했다. 회사는 프로파일러 오버헤드를 60% 넘게 줄였으며, 이 변경은 최신 ddtrace 버전에서 이용할 수 있다고 밝혔다.

기존 화염 그래프는 각 asyncio 작업을 별도 스택으로 표시해 작업이 어디서 실행되는지는 보여도 어떤 애플리케이션 경로가 그 작업을 일으켰는지는 드러내지 못했다. Datadog은 작업 간 의존 관계를 추적하고 관련 스택을 쌓아 표시하는 ‘stacked stacks’ 방식으로 이 맥락을 복원했다. 작업을 만든 코루틴과 실제로 기다리는 코루틴이 다를 수 있고 관계도 작업 수명 동안 바뀔 수 있어, 프로파일러는 샘플을 수집할 때 각 작업의 현재 의존 관계를 파악한다.

이 방식은 작업마다 메모리를 읽는 비용을 키웠다. Datadog은 샘플링 경로에서 C++ 예외를 없애 오버헤드를 15% 줄이고, 함수·파일 이름 문자열을 재사용해 추가로 25% 줄였지만, asyncio 작업이 늘면서 발생하는 시스템 호출 비용을 상쇄하기에는 부족했다고 설명했다. 작업 일부만 무작위로 샘플링하는 방법도 검토했으나, 일관된 작업 의존 트리를 만들려면 먼저 모든 작업의 메타데이터를 읽어야 해 오버헤드를 충분히 낮출 수 없었다.

대신 process_vm_readv를 보호된 memcpy 방식으로 바꿨다. 메모리 접근 중 오류가 나면 신호 처리기가 샘플링을 중단하고 오류를 반환하도록 해, 프로세스가 종료되지 않게 하는 구조다. 오류가 발생할 때는 복구 비용이 크지만, Datadog은 실제로 메모리 오류가 드물며 오류 없이 복사할 때는 memcpy에 가까운 비용이라고 설명했다. 이 변경으로 동기 코드의 프로파일러 오버헤드는 약 50% 감소했다.

Datadog은 내부 서비스에서 새 프로파일러를 사용해 Bits Chat API의 드문 요청 시간 초과 원인을 찾았다. 프로파일링 결과 pylzstr의 바쁜 루프가 드러났고, 조사 결과 일부 대규모 언어 모델 호출에서 반환된 잘못된 입력이 압축 해제 코드의 무한 루프를 일으키는 것으로 나타났다. Datadog은 버그를 보고하고 상류 프로젝트에 수정 사항을 기여했다.

또 다른 사례에서는 실제 입력으로 최적화한 데이터 배열 조작 함수들의 CPU 사용량이 함수별로 약 15~30% 줄었다. 이는 서비스 전체 CPU의 5%를 차지하던 함수에 적용돼 수백 개 워커에서 코어를 절약하는 결과로 이어졌다고 Datadog은 밝혔다. 프로파일러 최적화를 적용한 서비스에서는 오버헤드가 60% 넘게 줄었고, 샘플링 빈도가 최저 수준에 도달해 낮아졌던 프로파일 품질도 개선됐다고 설명했다.

이 개선 사항은 최신 ddtrace 버전에서 이용할 수 있다.