본문으로 건너뛰기

[Loki] 데이터 오브젝트 Plain Value 디코더 최적화로 처리량 93% 향상

PR 링크: grafana/loki#20441 상태: Merged | 변경: +422 / -265

들어가며

Grafana Loki의 데이터 오브젝트 레이어에서 Plain Value 디코더는 로그 데이터를 읽어 메모리에 로드하는 핵심 컴포넌트입니다. 이 PR은 디코더를 근본적으로 재설계하여, Arrow 스타일 메모리 표현, []byte 기반 디코딩, 포인터 간접 참조 최소화, 인터페이스 대신 구체적 타입 사용 등 여러 최적화를 적용합니다. 벤치마크 결과, geomean 기준 처리량이 약 93% 향상되었습니다.

핵심 코드 분석

페이지 읽기 방식 변경: io.Reader에서 []byte 직접 접근으로

Before:

func (p *MemPage) reader(compression datasetmd.CompressionType) (
    presence io.Reader, values io.ReadCloser, err error) {
    // io.Reader를 통한 스트림 읽기
    bitmapReader := bytes.NewReader(bitmapData)
    compressedValuesReader := bytes.NewReader(compressedValuesData)
    // ...
    return bitmapReader, io.NopCloser(compressedValuesReader), nil
}

After:

type openedPage struct {
    PresenceData []byte
    ValueData    []byte
}

func (p *MemPage) open(compression datasetmd.CompressionType) (
    openedPage, io.Closer, error) {
    // 직접 바이트 슬라이스 반환
    return openedPage{
        PresenceData: presenceData,
        ValueData:    compressedValuesData, // 또는 압축 해제된 데이터
    }, closer, nil
}

bufio.Reader 제거

Before:

type pageReader struct {
    presenceReader *bufio.Reader
    valuesReader   *bufio.Reader
    valuesDec      valueDecoder  // 인터페이스
}

After:

type pageReader struct {
    presenceDec *bitmapDecoder
    valuesDec   legacyValueDecoder  // 구체적 타입
    alloc       memory.Allocator    // 메모리 할당자 추가
}

bufio.Reader를 제거하고 바이트 슬라이스에 직접 접근함으로써, 버퍼 복사와 인터페이스 호출 오버헤드를 제거했습니다. 또한 memory.Allocator를 도입하여 매 read 호출마다 이전 할당을 회수하는 방식으로 메모리 관리를 개선했습니다.

벤치마크 결과

지표 Before After 개선
constant-size (sec/op) 257.9us 113.1us -56.17%
variable-size (sec/op) 102.07us 62.74us -38.54%
constant-size (rows/s) 77.54M 176.90M +128.15%
variable-size (rows/s) 38.13M 62.04M +62.71%
constant-size (allocs/op) 5 1 -80.00%
geomean (B/s) 11.48Gi 22.12Gi +92.67%

왜 이게 좋은가

  1. 근본적인 데이터 접근 패턴 변경: io.Reader 인터페이스 기반 스트림 읽기에서 []byte 직접 접근으로 전환하여, 함수 호출과 버퍼 복사 오버헤드를 제거했습니다.
  2. 메모리 할당 80% 감소: constant-size 케이스에서 5회 할당이 1회로 줄었습니다. GC 부하가 줄어 실환경에서 더 안정적인 레이턴시를 기대할 수 있습니다.
  3. Arrow 스타일 메모리 표현: 문자열 배열을 offset + data 형식으로 표현하여, 포인터 간접 참조와 개별 문자열 할당을 제거했습니다.
  4. 점진적 마이그레이션: 기존 구현과 새 구현을 공존시키는 레거시 어댑터를 두어, 엔드-투-엔드 성능 향상을 후속 PR에서 완성할 수 있도록 설계했습니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글