본문으로 건너뛰기

[Loki] Rate Batcher 도입으로 UpdateRates RPC 호출 대폭 감소

PR #20784 - feat: Introduce a rate batcher to batch rate updates

들어가며

Grafana Loki의 distributor에서 updateRates 요청이 매 push 요청마다 동기적으로 limits frontend에 전송되고 있었습니다. 이로 인해 RPC 호출 횟수가 O(D x P)/초(D: push 요청 수, P: 파티션 수)에 달하여 프론트엔드에 상당한 부하를 주고 있었습니다. 이 PR은 rateBatcher를 도입하여 rate 업데이트를 시간 윈도우 내에서 배치 처리합니다.

핵심 코드 분석

Before

func (t *DataObjTee) Duplicate(ctx context.Context, tenant string, streams []KeyedStream) {
    // ...
    rates, err := t.limitsClient.UpdateRates(ctx, tenant, segmentationKeyStreams)
    if err != nil {
        level.Error(t.logger).Log("msg", "failed to update rates", "err", err)
    }
    fastRates := make(map[uint64]uint64, len(rates))
    for _, rate := range rates {
        fastRates[rate.StreamHash] = rate.Rate
    }
    // ...
}

매 push마다 동기적으로 UpdateRates RPC를 호출합니다.

After

func (t *DataObjTee) Duplicate(ctx context.Context, tenant string, streams []KeyedStream) {
    // ...
    var fastRates map[uint64]uint64

    if t.rateBatcher != nil {
        // 배치 활성화: 배치에 추가하고 마지막 알려진 rate 반환
        fastRates = t.rateBatcher.Add(tenant, segmentationKeyStreams)
    } else {
        // 배치 비활성화: 기존 동기 호출
        fastRates = make(map[uint64]uint64, len(segmentationKeyStreams))
        rates, err := t.limitsClient.UpdateRates(ctx, tenant, segmentationKeyStreams)
        // ...
    }
    // ...
}

설정에서 rate_batch_window를 지정하면 rateBatcher가 활성화되어, 스트림 데이터를 시간 윈도우 동안 누적한 후 한 번에 UpdateRatesRaw로 전송합니다.

배치 윈도우 설정

distributor:
  dataobj-tee:
    rate_batch_window: 30s  # 0이면 배치 비활성화

왜 이게 좋은가

  1. RPC 호출 극적 감소: O(D x P)/초에서 O(P)/30초로 변경되어, 수천 배의 RPC 호출 감소가 가능합니다.
  2. 비동기 배치 처리: push 경로에서 rate 업데이트 대기 시간이 사라져 push 지연이 줄어듭니다.
  3. 서비스 수명 주기 관리: rateBatcher는 dskit의 services.Service 인터페이스를 구현하여, distributor의 서브서비스로 안전하게 관리됩니다.
  4. 하위 호환성: rate_batch_window: 0(기본값)이면 기존과 동일하게 동기 호출을 수행합니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글