Datadog / performance-optimization

9 개의 포스트

datadog

Steganography at scale: Embedding share URLs in Datadog widget screenshots (새 탭에서 열림)

데이터독(Datadog)은 사용자가 위젯을 스크린샷으로 캡처하더라도 쿼리, 시간 범위, 대시보드 설정과 같은 풍부한 컨텍스트를 보존할 수 있도록 픽셀 단위의 '보이지 않는 워터마크' 시스템을 구축했습니다. 위젯의 메타데이터 전체를 이미지에 직접 담는 대신, 해당 데이터를 저장한 Redis 캐시의 고유 키를 위젯 테두리 픽셀의 RGB 값을 미세하게 조정하여 인코딩하는 방식을 채택했습니다. 이를 통해 사용자 경험을 해치지 않으면서도 하루 10억 개 이상의 위젯에 대해 스크린샷만으로 원본 데이터에 접근할 수 있는 연결성을 제공합니다. **스크린샷의 한계와 워터마킹의 도입 배경** - 대시보드 위젯을 복사하여 붙여넣으면 라이브 프리뷰와 데이터 연결이 유지되지만, 많은 사용자는 직관적이고 권한 문제에서 자유로운 스크린샷 방식을 선호합니다. - 하지만 일반적인 스크린샷은 캡처 시점의 쿼리, 시간 범위, 시각화 유형 등 유용한 메타데이터를 모두 잃어버린다는 단점이 있습니다. - UI에 요소를 추가하지 않고도 이 정보를 보존하기 위해, 육안으로는 식별할 수 없지만 알고리즘으로 읽을 수 있는 픽셀 기반 워터마킹 기술을 도입했습니다. **데이터 최적화 및 캐싱 전략** - 위젯 정의 데이터는 평균 2KB로 이미지에 직접 인코딩하기에는 너무 크기 때문에, 전체 데이터는 Redis 캐시에 저장하고 이를 참조하는 짧은 고유 ID만 워터마크에 포함합니다. - 위젯이 렌더링될 때마다 프론트엔드에서 낙관적(Optimistic)으로 ID를 생성하여 백엔드 응답을 기다리지 않고 즉시 워터마크를 삽입함으로써 성능 저하를 방지합니다. - 조직 ID와 8바이트 무작위 ID를 조합하여 대규모 환경에서도 ID 충돌 가능성을 극도로 낮추었으며, 데이터는 스크린샷이 주로 활용되는 시간대를 고려해 1시간 동안 캐싱됩니다. **픽셀 레벨의 미세 인코딩 기법** - 모든 대시보드 위젯이 공통적으로 가진 1px 두께의 테두리를 데이터 삽입 공간으로 활용하여 시각화 유형에 상관없이 일관된 적용이 가능하게 했습니다. - RGB 모델의 각 채널 값을 미세하게 오프셋(Offset)하는 방식을 사용합니다. 기본 배경색에서 각 채널값을 조정한 뒤 0~7 사이의 값을 더해 픽셀당 약 9비트의 데이터를 저장합니다. - 워터마크의 시작과 끝을 알리는 센티널(Sentinel) 픽셀을 배치하고 그 사이에 8개의 데이터 픽셀을 넣어 총 8바이트의 ID를 인코딩하며, 이는 육안으로 거의 식별되지 않습니다. 이 시스템은 장애 대응이나 협업 과정에서 스크린샷이라는 익숙한 도구를 사용하면서도, 필요할 때 언제든 원본 데이터 컨텍스트로 복귀할 수 있는 강력한 연결성을 제공합니다. 대규모 트래픽 환경에서도 성능 영향 없이 작동하도록 설계된 이 기법은 단순한 이미지를 지능적인 데이터 포인터로 변환하는 실용적인 해법을 제시합니다.

datadog

How we built a Ruby library that saves 50% in testing time (새 탭에서 열림)

소프트웨어 프로젝트의 규모가 커짐에 따라 발생하는 길고 불안정한 CI 파이프라인은 개발 생산성을 저해하는 주요 원인입니다. 데이터독(Datadog)은 코드 변경 사항과 관련된 테스트만 선택적으로 실행하는 '테스트 영향 분석(Test Impact Analysis)' 기술을 통해 이 문제를 해결하고자 했으며, 성능 오버헤드를 최소화한 Ruby용 Intelligent Test Runner를 구축했습니다. 이를 위해 기존 도구들의 한계를 넘어 Ruby VM 인터프리터 이벤트를 직접 활용하는 C 익스텐션을 개발함으로써 테스트 시간을 절반으로 단축하는 성과를 거두었습니다. **테스트 영향 분석의 개념과 필요성** * CI 파이프라인의 병렬 실행은 속도를 높일 수 있지만, 클라우드 컴퓨팅 비용이 증가하고 관련 없는 코드의 결함으로 인한 테스트 실패(Flaky tests) 문제를 해결하지 못합니다. * 테스트 영향 분석은 각 테스트와 해당 테스트가 실행하는 소스 파일 간의 매핑 정보를 동적으로 생성하여 관리합니다. * Git 커밋에서 변경된 파일과 특정 테스트가 의존하는 파일 목록이 겹칠 때만 해당 테스트를 실행하고, 관련이 없는 경우 건너뜁니다. * 이 시스템은 정확성(필요한 테스트를 거르지 않음), 성능(매 커밋마다 실행 가능할 정도로 낮은 오버헤드), 투명성(사용자 코드 수정 없음)이라는 세 가지 핵심 요구사항을 충족해야 합니다. **기존 Ruby 솔루션의 한계** * **내장 Coverage 모듈:** Ruby 3.1에서 추가된 테스트별 커버리지 수집 기능은 `SimpleCov`와 같은 기존 커버리지 도구와 호환되지 않으며, 성능 오버헤드가 약 300%에 달해 테스트 속도가 4배나 느려지는 단점이 있습니다. * **TracePoint API:** VM 이벤트를 구독하는 `TracePoint` 방식은 사용이 간편하고 기존 도구와 충돌하지 않지만, 여전히 200~400% 수준의 높은 성능 저하를 유발하여 실제 개발 환경에 적용하기 어렵습니다. **Ruby VM 이벤트를 활용한 맞춤형 C 익스텐션** * 성능 최적화를 위해 Ruby 소스 코드의 `coverage.c`와 `thread.c`를 분석하여, C 언어 수준에서 직접 인터프리터 이벤트를 가로채는 방식을 채택했습니다. * Ruby의 C API인 `rb_add_event_hook2`를 사용하여 `RUBY_EVENT_LINE` 이벤트를 등록함으로써, 코드가 실행되는 시점에 즉각적으로 파일 정보를 수집하도록 설계했습니다. * `dd_cov_update_line_coverage`와 같은 콜백 함수 내에서 실행 중인 파일이 프로젝트 루트 내에 있는지 확인하는 필터링 로직을 구현하여 데이터 수집의 효율성을 높였습니다. * 이 접근 방식은 Ruby 인터프리터 내부 메커니즘을 직접 활용함으로써 성능 오버헤드를 획기적으로 낮추고, 대규모 테스트 수트에서도 무리 없이 작동합니다. 규모가 큰 Ruby 프로젝트에서 테스트 속도 정체와 CI 비용 증가 문제를 겪고 있다면, 전체 테스트를 매번 실행하는 대신 테스트 영향 분석 도구를 도입하여 파이프라인의 효율성을 극대화할 것을 권장합니다. 특히 성능이 중요한 환경이라면 Ruby 내장 도구에만 의존하기보다 VM 이벤트를 직접 제어하는 방식이 유효한 해결책이 될 수 있습니다.

datadog

How we built a Ruby library that saves 50% in testing time | Datadog (새 탭에서 열림)

개발 효율성을 저해하는 길고 불안정한 CI 파이프라인 문제를 해결하기 위해, 테스트와 소스 코드 간의 의존성을 분석하여 변경된 코드와 관련된 테스트만 선택적으로 실행하는 '테스트 영향 분석(Test Impact Analysis)' 기술이 주목받고 있습니다. Datadog은 Ruby 환경에서 이를 실현하기 위해 성능 저하를 최소화하면서도 기존 도구와 호환되는 전용 라이브러리를 개발하였으며, 이는 전체 테스트 시간을 절반 수준으로 단축하는 성과를 거두었습니다. 이 과정에서 개발 팀은 Ruby 내장 모듈의 한계를 극복하기 위해 C 확장을 통한 저수준 인터프리터 이벤트 활용 방식을 채택했습니다. ## 테스트 영향 분석(TIA)의 개념과 필요성 - 소프트웨어 규모가 커짐에 따라 전체 테스트 수트 실행 시간은 비대해지며, 코드 변경과 무관한 '불안정한 테스트(Flaky tests)'로 인해 CI가 실패하는 빈도가 높아집니다. - 테스트 영향 분석은 각 테스트가 실행될 때 접근하는 소스 파일 목록을 동적으로 맵핑하여 저장하는 기술입니다. - Git 커밋 시 변경된 파일과 맵핑된 파일 목록에 교집합이 있는 테스트만 실행함으로써, 불필요한 리소스 낭비를 줄이고 파이프라인의 안정성을 높일 수 있습니다. - Datadog의 'Intelligent Test Runner'는 이러한 원리를 바탕으로 정확성, 성능, 사용자 투명성을 핵심 가치로 설계되었습니다. ## 기존 Ruby 솔루션의 성능 한계 - **내장 Coverage 모듈:** Ruby 3.1에서 추가된 resume/suspend 메서드를 통해 테스트별 커버리지를 측정할 수 있으나, `simplecov`와 같은 기존 도구와 충돌하며 약 300% 수준의 매우 높은 성능 오버헤드가 발생합니다. - **TracePoint API:** 코드 실행 시 이벤트를 구독하는 표준 API로 구현이 용이하고 호환성도 뛰어나지만, 순수 코드 실행 위주의 벤치마크(RuboCop 등)에서 200~400%의 오버헤드를 기록하여 실무 적용이 어렵습니다. - 이러한 기존 방식들은 대규모 테스트 수트를 빠르게 실행하려는 원래의 목적에 부합하지 않는 성능 결과(기존보다 3~4배 느려짐)를 보였습니다. ## C 확장을 이용한 저수준 인터프리터 이벤트 활용 - 성능 문제를 해결하기 위해 Ruby VM의 내부 동작을 분석하고, C 언어로 직접 커버리지 수집 도구를 개발했습니다. - Ruby 인터프리터 내부에서 사용하는 `rb_thread_add_event_hook` 함수를 활용해 `RUBY_EVENT_LINE` 이벤트를 직접 훅(hook)하는 방식을 취했습니다. - 테스트 시작(start)과 종료(stop) 시점에만 이벤트 훅을 등록 및 해제하며, 실행되는 파일의 경로가 프로젝트 루트 내에 있는지 C 수준에서 빠르게 필터링하여 해시 구조에 저장합니다. - 이 방식은 Ruby 레벨의 추상화 단계를 건너뛰고 VM 이벤트에 직접 접근함으로써, 데이터 수집의 정확성을 유지하면서도 실행 오버헤드를 획기적으로 낮추는 기반이 되었습니다. Ruby 기반의 대규모 프로젝트를 운영 중이라면 매번 전체 테스트를 실행하기보다, 변경 사항에 기반한 지능형 테스트 실행 방식을 도입하여 CI 비용과 시간을 최적화할 것을 권장합니다. 특히 성능에 민감한 환경에서는 표준 API에 의존하기보다 저수준 최적화가 포함된 전문적인 모니터링 도구를 활용하는 것이 효과적입니다.

datadog

.NET Continuous Profiler: Memory usage (새 탭에서 열림)

Datadog의 .NET 프로파일러는 가비지 컬렉션(GC)의 효율성과 메모리 할당 패턴을 분석하여 애플리케이션의 성능 병목 현상을 진단합니다. 이 시스템은 모든 할당을 추적하는 대신 `AllocationTick` 이벤트를 활용한 샘플링 방식을 채택하여 운영 환경에서의 오버헤드를 최소화하면서도 정밀한 데이터를 제공합니다. 특히 .NET 7의 최신 API를 통해 객체의 생존 주기를 추적함으로써, CPU 부하의 원인이 되는 과도한 GC 작업과 잠재적인 메모리 누수 지점을 정확히 찾아내는 데 결론적인 도움을 줍니다. ### 가비지 컬렉터가 CPU에 미치는 영향 측정 * **전용 스레드 모니터링**: 서버 GC 설정 시 CLR이 생성하는 코어당 전용 스레드(.NET Server GC 및 .NET BGC)의 CPU 소비량을 운영체제로부터 직접 수집합니다. * **Pull 모델 채택**: GC 발생 시마다 이벤트를 받는 Push 방식과 달리, 프로파일러가 1분마다 주기적으로 GC 스레드의 CPU 사용 통계를 가져와 'Garbage Collector'라는 단일 프레임을 가진 네이티브 콜 스택 샘플로 기록합니다. * **버전별 차이**: .NET 5 이상에서는 GC 스레드 식별이 가능하여 정확한 측정이 가능하지만, 이전 버전에서는 스레드 ID 정보 부족으로 인해 이 기능을 완벽히 지원하기 어렵습니다. ### AllocationTick을 활용한 효율적인 할당 추적 * **샘플링 기반 추적**: 모든 객체 할당을 기록하는 `ObjectAllocated` 방식은 성능 저하가 극심하므로, 약 100KB의 할당이 누적될 때마다 발생하는 `AllocationTick` 이벤트를 사용하여 데이터를 수집합니다. * **상세 정보 수집**: 이벤트 페이로드에서 클래스 ID(ClassID), 타입 이름, 메모리 주소, 객체 크기뿐만 아니라 해당 객체가 할당된 힙의 종류(SOH, LOH, POH)까지 식별합니다. * **동기적 콜 스택 캡처**: 해당 이벤트는 할당을 수행한 스레드에서 동기적으로 발생하므로, 즉시 콜 스택을 워킹(Stack Walking)하여 어떤 비즈니스 로직이 메모리 압박을 유발하는지 특정할 수 있습니다. ### Weak Handle을 이용한 생존 객체 및 누수 탐지 * **객체 이동 대응**: GC의 컴팩션(Compaction) 단계에서 객체 주소가 변경되는 문제를 해결하기 위해, 샘플링된 객체에 대해 Weak 핸들을 생성하여 관리합니다. * **ICorProfilerInfo13 활용**: .NET 7에서 추가된 이 프로파일링 API를 통해, GC 이후에도 핸들이 가리키는 객체가 여전히 메모리에 살아있는지(`IsAllocated`)를 확인합니다. * **생명 주기 분석**: GC가 끝날 때마다 참조되지 않는 객체의 핸들은 제거하고, 생존한 객체들은 다음 프로필에 포함시켜 어떤 데이터가 메모리에 오래 머무르며 누수를 유발하는지 추적합니다. 운영 환경에서 메모리 문제를 분석할 때는 단순한 할당량 확인을 넘어, GC로 인한 CPU 점유율과 객체의 생존 기간을 함께 살펴야 합니다. 특히 .NET 7 이상의 최신 런타임을 활용하면 프로파일러의 Weak 핸들 추적 기능을 통해 메모리 누수 탐지의 정확도를 대폭 높일 수 있습니다.

datadog

How we brought Datadog's data visualization to iOS: A focus on performance (새 탭에서 열림)

Datadog은 복잡한 데이터 시각화를 iOS 모바일 앱에 네이티브로 구현하기 위해 자체 SwiftUI 기반 그래프 라이브러리인 'DogGraphs'를 개발했습니다. iOS 14 호환성을 유지해야 하는 제약 속에서 성능 병목을 해결하기 위해 SwiftUI의 렌더링 파이프라인과 디핑(Diffing) 메커니즘을 심도 있게 분석하고 최적화했습니다. 그 결과, 다양한 제품군에서 빠르고 유연하게 동작하며 컴파일 타임에 타입 안정성까지 보장하는 선언형 그래프 프레임워크를 구축할 수 있었습니다. ### DogGraphs 개발 배경과 도전 과제 * **자체 라이브러리 필요성**: 개발 당시 Swift Charts 같은 공식 라이브러리가 없었으며, Datadog 특유의 복잡한 데이터 시각화 요구사항을 충족하기 위해 직접 개발을 결정했습니다. * **하위 호환성 제약**: iOS 14를 지원해야 했기에 성능 최적화에 유리한 최신 `Canvas` API를 사용할 수 없었고, 표준 SwiftUI 뷰 계층 구조만으로 고성능을 구현해야 했습니다. * **선언형 API 설계**: Swift의 Result Builder를 활용해 SwiftUI와 유사한 구문으로 복잡한 그래프를 정의할 수 있게 했으며, 서로 다른 유형의 그래프를 잘못 쌓는 등의 실수를 컴파일 타임에 방지하도록 설계했습니다. ### 성능 분석 및 프로파일링 도구 활용 * **_printChanges() 활용**: 뷰의 `body` 내에서 이 비공개 API를 호출하여 어떤 상태 변화가 불필요한 재렌더링을 유발하는지 로그로 확인하고 디버깅했습니다. * **Xcode Instruments**: 'SwiftUI View body evaluations'를 통해 뷰의 바디가 평가되는 횟수와 평균 소요 시간을 측정했으며, 'Time profiler'로 실행 시간이 긴 함수를 찾아 최적화했습니다. * **주요 측정 시나리오**: 초기 그래프 렌더링 시점, 툴팁 선택이나 레이어 토글 같은 상호작용 발생 시, 기기 회전 및 다크/라이트 모드 전환 시의 성능을 집중적으로 점검했습니다. ### SwiftUI 핵심 개념과 디핑(Diffing) 메커니즘 * **렌더링 원리 이해**: SwiftUI의 성능 최적화를 위해 Identity(정체성), Lifetime(생명주기), Dependencies(의존성)라는 세 가지 핵심 개념을 기반으로 뷰 업데이트 방식을 분석했습니다. * **비트 단위 비교**: SwiftUI는 뷰의 필드를 비트 단위로 비교(memcmp)하여 이전 값과 차이가 없으면 `body`를 다시 계산하지 않고 건너跳는 최적화 방식을 사용합니다. * **의존성 관리**: 불필요한 의존성 전파를 막고 뷰 구조를 효율적으로 설계함으로써, 데이터 변경 시 영향을 받는 뷰만 정확히 다시 그려지도록 유도했습니다. ### 실용적인 권장 사항 복잡한 SwiftUI 애플리케이션의 성능을 높이려면 단순히 최신 기능을 사용하는 것에 그치지 말고, **뷰의 정체성(Identity)과 의존성 관계를 명확히 정의**해야 합니다. 특히 대규모 데이터를 다루는 시각화 도구에서는 SwiftUI의 내부 디핑 엔진이 효율적으로 작동할 수 있도록 뷰 모델과 프로퍼티 구조를 최적화하고, Instruments를 통해 렌더링 비용을 주기적으로 측정하는 과정이 필수적입니다.

datadog

.NET Continuous Profiler: CPU and wall time profiling | Datadog (새 탭에서 열림)

지속 프로파일링(Continuous Profiling)의 타임라인 뷰는 운영 환경의 애플리케이션에서 발생하는 미세한 성능 병목과 코드 비효율성을 시각적으로 진단할 수 있게 해줍니다. 기존의 플레임 그래프(Flame Graph)가 전체 실행 시간의 비중을 요약해서 보여준다면, 타임라인 뷰는 시간의 흐름에 따른 스레드별 활동과 자원 사용량을 매핑하여 간헐적인 지연이나 리소스 급증의 원인을 명확히 짚어냅니다. 이를 통해 개발자는 분산 추적(Tracing) 데이터와 개별 코드 실행 흐름을 연결하여 복잡한 런타임 문제를 더 신속하게 해결할 수 있습니다. ### 기존 프로파일링 방식의 한계와 타임라인 뷰의 등장 * **시계열 맥락의 부재**: 플레임 그래프는 특정 기간 내의 전체적인 코드 실행 비중을 보여주지만, 리소스 사용량이 특정 시점에 왜 급증했는지나 실행 순서에 따른 병목은 파악하기 어렵습니다. * **시간 축 기반 분석**: 타임라인 뷰는 X축을 시간으로, Y축을 개별 스레드나 프로세스로 구성하여 코드 실행의 흐름을 직관적으로 보여줍니다. * **데이터의 통합**: 메트릭(CPU/메모리 사용량), 로그, 트레이스를 단일 타임라인에 결합함으로써 특정 성능 저하가 발생한 순간에 어떤 코드가 실행되고 있었는지 즉각적인 확인이 가능합니다. ### CPU 시간과 벽시계 시간(Wall Time)의 차이 분석 * **실제 연산과 대기 시간 구분**: 타임라인 뷰는 스레드가 실제로 CPU를 점유하여 연산하는 시간(CPU Time)과 입출력(I/O)이나 락(Lock) 경합으로 대기하는 시간(Wall Time)을 명확히 구분합니다. * **I/O 병목 식별**: 특정 스레드가 오랜 시간 '대기' 상태로 표시된다면 외부 API 호출이나 데이터베이스 쿼리 응답을 기다리고 있음을 의미하며, 이는 코드 최적화보다 인프라나 네트워크 설정 검토가 필요함을 시사합니다. * **CPU 집약적 작업 포착**: 짧은 순간에 여러 스레드에서 CPU 사용량이 치솟는 구간을 확인하여 복잡한 알고리즘이나 무한 루프와 같은 코드 결함을 찾아낼 수 있습니다. ### 분산 추적(Tracing)과의 긴밀한 연동 * **Span ID 기반 드릴다운**: 특정 요청의 트레이스 정보(Span)를 프로파일러의 타임라인과 연결하여, 해당 요청이 처리되는 동안 각 스레드에서 어떤 함수가 호출되었는지 상세히 추적합니다. * **컨텍스트 스위칭 파악**: 하나의 요청이 여러 스레드를 거쳐 처리될 때 발생하는 컨텍스트 스위칭 비용이나 스레드 풀의 효율성을 시각적으로 검증할 수 있습니다. * **비정상적인 지연 탐지**: 전체 요청 시간은 짧지만 특정 구간에서 비정상적으로 긴 실행 시간이 소요되는 '롱 테일(Long tail)' 문제를 코드 수준에서 진단합니다. ### 가비지 컬렉션(GC) 및 런타임 오버헤드 진단 * **Stop-The-World 감지**: 가비지 컬렉션으로 인해 모든 애플리케이션 스레드가 일시 정지되는 구간을 타임라인에서 명확히 확인하여 메모리 할당 효율성을 평가할 수 있습니다. * **락 경합(Lock Contention) 해소**: 여러 스레드가 동일한 자원에 접근하기 위해 대기하는 구간을 시각화하여, 동기화 로직의 병목을 찾아내고 동시성 제어 구조를 개선할 수 있는 근거를 제공합니다. 운영 환경의 성능 문제를 해결하기 위해서는 단순히 "무엇이 느린가"를 넘어 "언제, 왜 느려졌는가"에 대한 답이 필요합니다. 지속 프로파일러의 타임라인 뷰를 활용하면 높은 수준의 추상화된 메트릭에서 시작해 실제 코드 실행의 세부 디테일까지 단절 없이 탐색할 수 있습니다. 특히 간헐적으로 발생하는 성능 저하를 재현하기 어려운 환경에서, 타임라인 뷰는 실행 시점의 스냅샷을 제공하여 근본 원인 분석(RCA)의 시간을 획기적으로 단축해 줄 것입니다.

datadog

Making fetch happen: Building a general-purpose query and render scheduler (새 탭에서 열림)

데이터독(Datadog)은 복잡한 대시보드의 성능 최적화를 위해 쿼리와 렌더링 작업을 효율적으로 관리하는 범용 스케줄러를 개발했습니다. 기존의 복잡한 규칙 기반 시스템을 단순화하고 브라우저 네이티브 API를 활용함으로써, UI 응답성을 높이고 백엔드 부하를 안정화하는 성과를 거두었습니다. 이 글은 기존 스케줄러의 한계를 극복하고 모든 애플리케이션에 적용 가능한 유연한 스케줄링 모델로 전환한 과정을 다룹니다. ### 기존 스케줄러(v1)의 문제점 * **지나친 복잡성:** 약 20여 개의 매개변수가 얽힌 복잡한 휴리스틱(heuristics)으로 운영되어, 개발자가 시스템의 동작 방식을 예측하거나 추론하기 어려웠습니다. * **로직의 결합:** 쿼리(데이터 호출)와 렌더링(화면 표시) 스케줄링이 명확히 분리되지 않았습니다. 예를 들어, 렌더링 대기 작업이 많다는 이유로 연관 없는 쿼리 요청이 지연되는 등의 비효율이 발생했습니다. * **낮은 범용성:** 대시보드 환경에만 특화되어 설계되었기 때문에, 데이터독의 다른 제품군이나 일반적인 위젯 컴포넌트에서 재사용하기 어려운 구조적 한계가 있었습니다. ### 데이터 쿼리 스케줄링의 단순화 * **가시성 기반 우선순위:** 화면에 보이는(visible) 위젯의 쿼리는 요청 즉시 실행하고, 화면 밖(offscreen) 위젯은 별도의 대기열에 추가하여 지연 처리합니다. * **고정 시간 윈도우 알고리즘:** 복잡한 계산 대신 2,000ms라는 고정된 시간 창(window) 동안 최대 10개의 태스크만 실행하도록 제한하여 작업 분포를 평탄화했습니다. * **대기열 관리 최적화:** 백엔드에 펜딩된 요청이 너무 많으면 실행을 일시 중단하며, 화면 밖 작업들은 대기열에 들어온 순서대로 처리하여 공정성을 유지합니다. * **도입 결과:** 로직이 단순해졌음에도 불구하고 서버의 '429(Too many requests)' 에러가 크게 감소했으며, 쿼리 재시도 횟수가 줄어들어 전체적인 데이터 로딩 속도가 향상되었습니다. ### Browser Scheduling API를 활용한 렌더링 최적화 * **자원 상태 인지:** 기존 스케줄러는 브라우저의 CPU나 메모리 가용 상태를 알지 못한 채 작업을 할당했으나, 새로운 시스템은 브라우저 네이티브 기능을 활용합니다. * **우선순위 세분화:** 최신 `Browser Scheduling API`의 `postTask` 메서드를 도입하여 작업의 성격에 따라 우선순위(user-blocking, user-visible, background)를 부여합니다. * **효율적인 메인 스레드 사용:** 브라우저가 직접 작업의 우선순위를 제어하게 함으로써, 중요한 UI 업데이트는 즉시 처리하고 덜 중요한 렌더링은 브라우저가 유휴 상태일 때 실행하도록 최적화했습니다. 복잡한 웹 애플리케이션에서 성능을 확보하려면 스케줄링 로직을 최대한 단순화하고 브라우저가 제공하는 표준 API를 적극적으로 활용해야 합니다. 이는 개발자에게는 시스템에 대한 통제력을 제공하며, 사용자에게는 더 매끄러운 인터랙션 경험을 선사합니다.

datadog

Performance improvements in the Datadog Agent metrics pipeline (새 탭에서 열림)

Datadog Agent는 동일한 CPU 리소스로 더 많은 메트릭을 빠르게 처리하기 위해 메트릭 고유 키(Context) 생성 로직을 최적화했습니다. Go 언어의 프로파일링 도구를 통해 태그 정렬 및 해싱 과정이 시스템의 주요 병목 지점임을 확인했으며, 이를 해결하기 위해 상황별 특수화 알고리즘과 64비트 해시 최적화 기법을 도입했습니다. 이러한 개선을 통해 에이전트의 데이터 처리 성능을 한 단계 높이고 리소스 효율성을 극대화하는 결과를 얻었습니다. ### 병목 지점 식별 및 분석 * Go 언어(Golang)의 CPU 프로파일링과 플레임그래프(Flamegraph) 도구를 활용하여 메트릭 파이프라인 내 리소스 소모가 큰 지점을 추적했습니다. * 분석 결과, 메트릭을 수신하고 고유 키를 생성하는 `addSample` 및 `trackContext` 함수가 가장 많은 CPU를 점유하고 있음을 확인했습니다. * 특히 태그 중복을 제거하고 동일한 해시 값을 보장하기 위해 수행하는 태그 정렬 로직(`util.SortUniqInPlace`)이 전체 성능의 주요 장애물로 작용하고 있었습니다. ### 메트릭 컨텍스트 생성의 기술적 문제 * 고유 식별을 위해 메트릭 이름, DogStatsD 태그, 컨테이너 태그를 모두 조합하여 해시 키를 생성해야 합니다. * 해시 충돌을 방지하면서도 빠른 생성 속도를 유지해야 하며, 동일한 메트릭에 대해 항상 일관된 키를 생성하기 위해 태그 리스트를 정렬하는 과정이 필수적이었습니다. * 태그 리스트 정렬은 데이터 양이 많아질수록 비용이 급격히 증가하는 특성이 있어, 매번 메트릭이 들어올 때마다 이를 수행하는 것은 비효율적이었습니다. ### 성능 최적화를 위한 다각도 접근 * **코드 특수화(Specialization):** 모든 경우에 일반적인 정렬 알고리즘을 사용하는 대신, 태그의 개수에 따라 가장 빠른 성능을 낼 수 있는 정렬 방식을 선택적으로 적용하도록 로직을 개선했습니다. * **해시 알고리즘 및 구조 개선:** 벤치마크를 통해 속도와 고유성이 검증된 Murmur3 알고리즘을 도입했습니다. * **Go 런타임 최적화 활용:** 기존 128비트 해시를 충돌 방지에 충분한 64비트로 전환하여, Go 런타임의 최적화된 맵 접근 함수(`mapassign_fast64`, `mapaccess2_fast64`)가 동작하도록 유도함으로써 처리 속도를 가속화했습니다. 데이터 집약적인 시스템에서는 런타임 프로파일링을 통해 '핫 패스(Hot path)'를 정확히 찾아내는 것이 중요합니다. 특히 태그 정렬이나 해싱과 같은 빈번한 기본 연산에서 발생하는 미세한 오버헤드를 줄이는 것만으로도 대규모 환경에서의 전체 처리량(Throughput)을 크게 향상시킬 수 있습니다.

datadog

Using Datadog APM to improve the performance of Homebrew (새 탭에서 열림)

이 글은 Datadog의 소프트웨어 엔지니어링 인턴인 Andrew Robert McBurney가 오픈 소스 프로젝트인 Homebrew의 `brew linkage` 명령어 성능을 개선한 과정을 다룹니다. Datadog의 APM 도구와 플레임 그래프를 활용해 병목 지점을 정확히 파악했으며, 캐싱 메커니즘을 도입하여 패키지 검사 속도를 획기적으로 향상시켰습니다. 결과적으로 106개 패키지 처리 시간을 11.5초에서 182밀리초로 단축하며 프로젝트의 요구 성능을 성공적으로 충족했습니다. ### APM을 활용한 성능 병목 지점 탐색 * Datadog의 `ddtrace` 젬(gem)을 사용해 Homebrew의 루비 코드를 인스트루먼테이션(instrumentation)하여 실행 데이터를 수집했습니다. * 수집된 데이터를 플레임 그래프로 시각화하여 분석한 결과, `LinkageChecker::check_dylibs` 함수가 전체 실행 시간의 대부분을 차지하는 병목 지점임을 확인했습니다. * 이 함수는 패키지의 동적 라이브러리 링크를 확인하고 이를 시스템 라이브러리, 깨진 링크, 선언되지 않은 의존성 등으로 분류하는 복잡한 작업을 수행합니다. ### 멀티스레딩의 한계와 캐싱 전략 도입 * 초기에는 루비의 `Thread` 프리미티티브를 이용한 멀티스레딩으로 성능 개선을 시도했으나, 루비의 **GIL(Global Interpreter Lock)** 제한으로 인해 기대했던 성능 향상을 얻지 못했습니다. * 대안으로 SQLite3를 이용한 온디스크(on-disk) 캐싱 메커니즘을 설계하여, 한 번 계산된 연결성 정보를 영구 저장하고 재사용하도록 했습니다. * SQLite3 기반 캐싱 도입 후, `boost` 라이브러리 검사 시간이 1.01초에서 1.38밀리초로 줄어드는 등 전체적인 명령 처리 속도가 비약적으로 개선되었습니다. ### 의존성을 고려한 PStore 기반 최종 최적화 * Homebrew 유지관리자의 피드백을 반영하여, 외부 의존성인 SQLite3 젬 대신 루비 표준 라이브러리인 **PStore**로 캐싱 구현을 교체했습니다. * PStore는 루비 객체를 파일에 영구적으로 저장하는 해시 기반의 메커니즘으로, 추가적인 외부 라이브러리 설치 없이도 SQLite3와 유사한 성능 이점을 제공합니다. * 이 과정을 통해 대규모 오픈 소스 프로젝트에서는 성능 최적화만큼이나 의존성 관리와 표준 라이브러리 활용이 중요하다는 점을 입증했습니다. ### 실용적인 결론 성능 최적화 시 직관에 의존하기보다 APM과 같은 도구를 사용하여 데이터 기반으로 병목을 찾는 것이 중요합니다. 특히 루비 환경에서는 GIL로 인해 병렬 처리가 제한적일 수 있으므로, 연산량이 많은 작업은 캐싱을 통해 중복 계산을 피하는 것이 가장 효과적인 전략이 될 수 있습니다. 또한, 오픈 소스 기여 시에는 프로젝트의 경량성을 유지하기 위해 가급적 표준 라이브러리(예: PStore)를 활용하는 것을 권장합니다.