[Loki] 테넌트 rate limit 기반 셔플 샤딩으로 쿼리 성능 향상
PR 링크: grafana/loki#19990 상태: Merged | 변경: +195 / -34
들어가며
Grafana Loki에서 {service_name=~"foo|bar"}처럼 여러 세그먼트 키를 동시에 쿼리할 때, 해당 키들이 서로 다른 파티션에 분산되어 있으면 더 많은 데이터 오브젝트를 읽어야 합니다. 이 PR은 테넌트의 ingestion rate limit을 기반으로 셔플 샤딩(shuffle sharding)을 적용하여, 저볼륨 테넌트의 세그먼트 키들을 더 적은 수의 파티션에 집중시킵니다.
핵심 코드 분석
2단계 셔플 샤딩
Before (단일 레벨 샤딩):
func (r *SegmentationPartitionResolver) Resolve(
_ context.Context, key SegmentationKey, rateBytes uint64,
) (int32, error) {
ring := r.ringReader.PartitionRing()
// 세그먼트 키의 rate로 직접 셔플 샤딩
partitions := rateBytes / r.perPartitionRateBytes
subring, err := ring.ShuffleShard(string(key), int(partitions))
// ...
}
After (2단계: 테넌트 -> 세그먼트 키):
func (r *SegmentationPartitionResolver) Resolve(
ctx context.Context, tenant string, key SegmentationKey,
rateBytes, tenantRateBytes uint64,
) (int32, error) {
ring := r.ringReader.PartitionRing()
// 1단계: 테넌트 rate limit으로 서브링 생성
subring, err := r.getTenantSubring(ctx, ring, tenant, tenantRateBytes)
if err != nil { return 0, err }
// rate 미확인 시 서브링 내에서 랜덤 선택
if rateBytes == 0 {
activePartitionIDs := subring.ActivePartitionIDs()
// 랜덤 셔플 후 첫 번째 선택
return activePartitionIDs[0], nil
}
// 2단계: 세그먼트 키 rate로 추가 서브링 생성
subring, err = r.getSegmentationKeySubring(ctx, subring, key, rateBytes)
// ...
}
테넌트 서브링 크기 계산
func (r *SegmentationPartitionResolver) getTenantSubring(
_ context.Context, ring *ring.PartitionRing,
tenant string, tenantRateBytes uint64,
) (*ring.PartitionRing, error) {
if tenantRateBytes == 0 {
return ring, nil // 제한 없으면 전체 링 사용
}
partitions := tenantRateBytes / r.perPartitionRateBytes
partitions = max(partitions, 1) // 최소 1개
partitions = min(partitions, uint64(ring.ActivePartitionsCount()))
return ring.ShuffleShard(tenant, int(partitions))
}
왜 이게 좋은가
- 데이터 지역성 향상: 저볼륨 테넌트의 세그먼트 키들이 더 적은 수의 파티션에 집중되어,
service_name=~"foo|bar"같은 다중 키 쿼리 시 읽어야 할 데이터 오브젝트 수가 줄어듭니다. - 2단계 설계: 테넌트 레벨에서 먼저 파티션 범위를 좁히고, 그 안에서 세그먼트 키별로 다시 샤딩하므로, 각 레벨의 부하 분산이 독립적으로 동작합니다.
- rate limit 비례: ingestion rate가 높은 테넌트는 더 많은 파티션을 사용하고, 낮은 테넌트는 적은 파티션에 집중되므로, 리소스 사용이 자연스럽게 비례합니다.
- 방어적 처리:
tenantRateBytes가 0이면 전체 링을 사용하고, 음수 변환 방지를 위해max(rate, 0)을 적용했습니다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Grafana Loki] 파서의 문자열 인턴 셋에서 키 충돌 결과 캐싱 버그 수정
- 현재글 : [Loki] 테넌트 rate limit 기반 셔플 샤딩으로 쿼리 성능 향상
- 다음글 [Triton] AMD TDM 연산에 multi-CTA 및 multicast 지원 추가
댓글