본문으로 건너뛰기

[Loki] Kafka 파티션 불필요한 Shuffle Sharding 제거

PR 링크: grafana/loki#21344 상태: Merged | 변경: +91 / -18

들어가며

Grafana Loki의 distributor에서 Kafka로 로그를 전송할 때, 테넌트별로 partition ring의 shuffle shard를 생성합니다. 그런데 IngestionPartitionsTenantShardSize가 0인 경우(모든 파티션 사용)에도 불필요하게 ShuffleShard()를 호출하여 전체 파티션 링의 복사본을 생성하고 캐싱하는 문제가 있었습니다. 테넌트 수가 많으면 이 낭비되는 메모리가 상당했습니다.

핵심 코드 분석

Before: 항상 ShuffleShard 호출

if d.cfg.KafkaEnabled {
    subring, err := d.partitionRing.PartitionRing().ShuffleShard(
        tenantID,
        d.validator.IngestionPartitionsTenantShardSize(tenantID),
    )
    if err != nil {
        return nil, err
    }
    d.sendStreamsToKafka(ctx, streams, tenantID, &tracker, subring)
}

ShardSize가 0이든 아니든 항상 ShuffleShard()를 호출합니다. 이 호출은 내부적으로 PartitionRing의 전체 복사본을 생성하고 테넌트별로 캐싱합니다.

After: ShardSize가 0이면 원본 링 직접 사용

if d.cfg.KafkaEnabled {
    shardSize := d.validator.IngestionPartitionsTenantShardSize(tenantID)
    var subring *ring.PartitionRing
    if shardSize == 0 {
        // Optimization - don't need to create shuffle shards in this case
        subring = d.partitionRing.PartitionRing()
    } else {
        subring, err = d.partitionRing.PartitionRing().ShuffleShard(
            tenantID, shardSize,
        )
        if err != nil {
            return nil, err
        }
    }
    d.sendStreamsToKafka(ctx, streams, tenantID, &tracker, subring)
}

shardSize == 0이면 전체 파티션을 사용하겠다는 의미이므로, 원본 PartitionRing을 그대로 사용합니다. 불필요한 복사와 캐싱을 완전히 건너뜁니다.

왜 이게 좋은가

  1. 메모리 절감: 테넌트가 수천 개일 때 각 테넌트마다 전체 PartitionRing의 복사본을 캐싱하지 않아 상당한 메모리를 절약합니다.
  2. CPU 절감: ShuffleShard() 내부의 해싱, 정렬, 토큰 계산 작업을 생략합니다.
  3. 동작 동일성: ShuffleShard(tenantID, 0)의 결과는 원본 링과 동일하므로, 기능적 변경이 없는 순수한 최적화입니다.

이 PR은 테스트 코드도 함께 추가하여 shardSize=0일 때 모든 파티션에 데이터가 분산되는 것을 검증합니다. 조건부 최적화의 모범적인 패턴입니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글