본문으로 건너뛰기

[Loki] 청크 재정렬 시 파이프라인 처리 바이패스로 CPU 최적화

PR 링크: grafana/loki#19211 상태: Merged | 변경: +46 / -3

들어가며

Loki 클러스터에서 청크 쓰기 경로에 많은 CPU 시간이 소비되고 있었다. 프로파일링 결과, 청크 재정렬(reorder) 시 사용하는 이터레이터가 쿼리용 파이프라인과 동일한 것을 사용하고 있어, NoopPipeline임에도 structured metadata 라벨 파싱과 관련 할당이 발생하고 있었다.

핵심 코드 분석

Context 기반 처리 비활성화 힌트

type processingDisabledKey struct{}

func processingDisabledContext(parent context.Context) context.Context {
    return context.WithValue(parent, processingDisabledKey{}, true)
}

func isProcessingDisabled(ctx context.Context) bool {
    v := ctx.Value(processingDisabledKey{})
    disabled, _ := v.(bool)
    return disabled
}

reorder에서 처리 비활성화 컨텍스트 사용

func (c *MemChunk) reorder() error {
    from, to := c.Bounds()
    ctx := processingDisabledContext(context.Background())
    itr, err := c.Iterator(ctx, from, to.Add(time.Millisecond),
        logproto.FORWARD, log.NewNoopPipeline().ForStream(labels.Labels{}))
    // ...
}

이터레이터에서 조건부 처리 바이패스

type entryBufferedIterator struct {
    // ...
    skipProcessing bool
}

func (e *entryBufferedIterator) Next() bool {
    for e.bufferedIterator.Next() {
        if e.skipProcessing {
            e.cur.Timestamp = time.Unix(0, e.currTs)
            e.cur.Line = string(e.currLine)
            e.cur.StructuredMetadata = logproto.FromLabelsToLabelAdapters(e.currStructuredMetadata)
            return true
        }

        newLine, lbs, matches := e.pipeline.Process(e.currTs, e.currLine, e.currStructuredMetadata)
        // ...
    }
}

왜 이게 좋은가

  1. 불필요한 처리 제거: 재정렬은 로그 라인의 순서만 바꾸면 되는데, 기존에는 각 라인에 대해 파이프라인 처리(라벨 파싱, 메타데이터 처리)를 수행하고 있었다.
  2. Context 패턴 활용: Go의 context.Value를 활용한 힌트 전달로, 기존 인터페이스를 변경하지 않고도 동작을 제어할 수 있다.
  3. 쓰기 경로 최적화: 청크가 out-of-order로 들어올 때마다 호출되는 재정렬 경로를 최적화하여, 고부하 환경에서의 CPU 사용량을 줄인다.
  4. 안전한 바이패스: 라벨이 변경되지 않는다는 것을 알고 있으므로, pipeline.BaseLabels()를 한 번만 호출하여 재사용한다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글