[loki] Grafana Loki 엔진의 집계 성능 최적화: 메모리 할당 감소와 효율적인 라벨 처리
PR 링크: grafana/loki#22526 상태: Merged | 변경: +290 / -240
들어가며
Grafana Loki는 대규모 로그 데이터를 처리하는 분산 시스템입니다. 최근 Loki 엔진의 RangeAggregation 및 VectorAggregation 과정에서 발생하는 성능 병목을 해결하기 위한 최적화 작업이 진행되었습니다. 기존 구현에서는 라벨 값을 처리할 때 불필요한 해싱과 메모리 할당이 빈번하게 발생하여, 특히 대규모 시리즈(series)를 집계할 때 CPU와 메모리 사용량이 급증하는 문제가 있었습니다. 본 글에서는 이번 PR에서 적용된 핵심 최적화 기법들을 살펴봅니다.
코드 분석
1. 라벨 캐싱 제거 및 String Interning 도입
기존에는 라벨 값을 캐싱하기 위해 clonedLabelValues 맵을 사용했습니다. 이는 매번 라벨을 해싱하고 복제하는 비용을 발생시켰습니다. 이를 제거하고 interner 구조체를 도입하여 메모리 할당을 최소화했습니다.
Before:
cloned, ok := a.clonedLabelValues[v]
if !ok {
cloned = strings.Clone(v)
a.clonedLabelValues[v] = cloned
}
series[labels[i].Name] = cloned
After:
internedLabel := a.interner.Get(labels[i].Name)
internedValue := a.interner.Get(v)
series[internedLabel] = internedValue
2. AddN 메서드 도입을 통한 배치 처리
단일 타임스탬프를 처리하던 Add 메서드 대신, 여러 타임스탬프를 한 번에 처리하는 AddN 메서드를 도입했습니다. 이를 통해 동일한 라벨 그룹에 대해 해시 키를 한 번만 계산하고, 여러 타임스탬프에 대해 루프를 돌며 집계함으로써 오버헤드를 대폭 줄였습니다.
After (AddN):
// Calculate the hash key for all windows once
// ... (digest calculation) ...
// Add all observations to the aggregation
for _, ts := range timestamps {
a.add(ts, value, key)
}
왜 이게 좋은가
이번 최적화는 벤치마크 결과에서 극적인 성능 향상을 보여주었습니다. 특히 100k_series 환경에서 sec/op 기준 약 37%의 성능 개선과 allocs/op 기준 약 46%의 메모리 할당 감소를 달성했습니다.
핵심 교훈
- 중복 해싱 제거: 동일한 데이터에 대해 여러 번 해싱하는 로직은 성능의 주범입니다. 캐싱 구조를 단순화하고 데이터 흐름을 최적화하는 것만으로도 큰 이득을 볼 수 있습니다.
- 배치 처리의 힘: 개별 호출을 줄이고 데이터를 모아서 처리하는(Batching) 전략은 특히 Go와 같은 언어에서 함수 호출 오버헤드와 메모리 할당을 줄이는 데 매우 효과적입니다.
- String Interning: 반복되는 문자열(라벨 이름 등)을 재사용함으로써 힙 할당을 줄이고 GC(Garbage Collector)의 부담을 덜어줄 수 있습니다.
리뷰 과정에서 bboreham은 "키가 작은 정수라면 맵 대신 배열을 사용하는 것이 더 빠를 수 있다"는 통찰을 제공했으며, 이는 향후 추가적인 최적화 포인트로 고려될 수 있습니다.
참고 자료
⚠️ 알림: 이 분석은 AI가 실제 코드 diff를 기반으로 작성했습니다.
관련 포스트
PR Analysis 의 다른글
- 이전글 [sglang] SGLang 성능 최적화: D2H 복사 연산의 비동기 오버랩 구현
- 현재글 : [loki] Grafana Loki 엔진의 집계 성능 최적화: 메모리 할당 감소와 효율적인 라벨 처리
- 다음글 [sglang] SGLang의 Qwen3.5 성능 극대화: Fused QK GemmaRMSNorm + RoPE 커널 최적화 분석
댓글