본문으로 건너뛰기

[Grafana Loki] 쿼리 엔진 aggregator의 자료구조를 개선하여 38% 성능 향상

PR 링크: grafana/loki#20934 상태: Merged | 변경: +45 / -62

들어가며

Grafana Loki의 쿼리 엔진에서 aggregator는 벡터 집계와 범위 집계 모두에 사용되는 핵심 컴포넌트입니다. 이 PR은 세 가지 자료구조 최적화로 메트릭 쿼리 전체 테스트 스위트 실행 시간을 1분 1초에서 38초로 38% 단축했습니다.

핵심 코드 분석

Before: groupState에 라벨 데이터 포함 + 리스트 기반 라벨 관리

type groupState struct {
    value       float64
    count       int64
    labels      []arrow.Field   // 매 그룹마다 라벨 필드 복사
    labelValues []string        // 매 그룹마다 라벨 값 복사
}

type aggregator struct {
    labels       []arrow.Field              // 리스트 기반
    uniqueSeries map[uint64]struct{}         // 키만 저장
}

// AddLabels: O(n*m) 중복 검사
func (a *aggregator) AddLabels(labels []arrow.Field) {
    for _, label := range labels {
        if !slices.ContainsFunc(a.labels, func(l arrow.Field) bool {
            return label.Equal(l)
        }) {
            a.labels = append(a.labels, label)
        }
    }
}

// BuildRecord: O(labels * groups) 선형 탐색
for i, label := range a.labels {
    j := slices.IndexFunc(entry.labels, func(l arrow.Field) bool {
        return l.Name == label.Name
    })
    // ...
}

After: 라벨 데이터 분리 + 맵 기반 관리

type groupState struct {
    value float64   // 집계 값만 보유
    count int64
}

type aggregator struct {
    labels       map[string]arrow.Field              // 맵 기반 O(1) 조회
    uniqueSeries map[uint64]map[string]string         // 시리즈별 라벨 값 저장
}

// AddLabels: O(1) 중복 검사
func (a *aggregator) AddLabels(labels []arrow.Field) {
    for _, label := range labels {
        if _, ok := a.labels[label.Name]; !ok {
            a.labels[label.Name] = label
        }
    }
}

// BuildRecord: O(1) 맵 조회
series := a.uniqueSeries[key]
for i := 2; i < len(fields); i++ {
    if v, ok := series[fields[i].Name]; ok {
        builder.(*array.StringBuilder).Append(v)
    } else {
        builder.(*array.StringBuilder).AppendNull()
    }
}

왜 이게 좋은가

  • 38% 성능 향상: 메트릭 쿼리 전체 테스트 실행 시간이 1분 1초에서 38초로 단축됩니다.
  • O(n*m) -> O(1): BuildRecord에서 라벨 이름별 선형 탐색이 맵 조회로 대체됩니다.
  • 메모리 효율: 매 groupState마다 라벨 필드와 값을 복사하지 않고, 시리즈 단위로 한 번만 저장합니다.
  • 중복 저장 제거: 같은 시리즈의 서로 다른 타임스탬프에 대해 라벨 값이 한 번만 저장됩니다.
  • 실행 시간 측정 수정: 범위/벡터 집계에서 BuildRecord 시간이 cap 측정에 포함되지 않던 버그도 함께 수정되었습니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글