[Loki] Delta Decoder 최적화로 3배 처리량 개선
PR 링크: grafana/loki#20451 상태: Merged | 변경: +81 / -58
들어가며
Grafana Loki의 데이터 객체 레이어에서 사용하는 delta decoder는 시계열 데이터의 타임스탬프를 효율적으로 저장하기 위해 사용됩니다. 기존 구현은 streamio.Reader 인터페이스를 통해 데이터를 읽었는데, 이 인터페이스의 추상화 비용이 상당했습니다. 이 PR은 인터페이스를 제거하고 바이트 슬라이스에 직접 접근하는 방식으로 변경하여 약 60%의 성능 향상(geomean 기준)을 달성했습니다.
핵심 코드 분석
Before: streamio.Reader 인터페이스 사용
type deltaDecoder struct {
r streamio.Reader
prev int64
}
func (dec *deltaDecoder) decode() (Value, error) {
delta, err := streamio.ReadVarint(dec.r)
if err != nil {
return Int64Value(dec.prev), err
}
dec.prev += delta
return Int64Value(dec.prev), nil
}
streamio.Reader 인터페이스를 통한 간접 호출이 매 varint 읽기마다 발생합니다. 또한 Value 타입으로의 변환도 각 값마다 수행됩니다.
After: 직접 바이트 슬라이스 접근 + 배치 디코딩
type deltaDecoder struct {
buf []byte
off int
prev int64
}
func (dec *deltaDecoder) Decode(alloc *memory.Allocator, count int) (any, error) {
valuesBuf := buffer.WithCapacity[int64](alloc, count)
valuesBuf.Resize(count)
values := valuesBuf.Data()
buf := dec.buf
prev := dec.prev
off := dec.off
defer func() { dec.buf = buf; dec.prev = prev; dec.off = off }()
for i := range count {
delta, n := binary.Varint(buf[off:])
if n <= 0 {
valuesBuf.Resize(i)
return values[:i], io.EOF
}
off += n
prev += delta
values[i] = prev
}
return values, nil
}
핵심 최적화 포인트:
binary.Varint로 바이트 슬라이스에서 직접 varint를 읽어 인터페이스 디스패치 제거- 로컬 변수 섀도잉(
buf,prev,off)으로 포인터 역참조 회피 []int64슬라이스로 직접 디코딩하여Value래핑 제거
왜 이게 좋은가
벤치마크 결과가 인상적입니다:
| 시나리오 | 개선폭 (sec/op) | 처리량 증가 (B/s) |
|---|---|---|
| sequential | -51% | +105% |
| largest_delta | -66% | +188% |
| random | -60% | +147% |
| geomean | -60% | +147% |
Go에서 인터페이스를 통한 메서드 호출은 직접 호출 대비 간접 점프와 escape analysis 실패로 인한 힙 할당이 발생할 수 있습니다. 이 PR은 hot path에서 인터페이스를 제거하고 직접 접근으로 변경하여 큰 성능 개선을 이끌어낸 좋은 사례입니다. 다만 메모리 할당 횟수는 증가했는데, 이는 배치 단위의 []int64 버퍼 할당 때문이며 전체 처리량 대비 무시할 수 있는 수준입니다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [triton] moveUpTranspose 최적화 제거 PR의 Revert - 회귀 방지
- 현재글 : [Loki] Delta Decoder 최적화로 3배 처리량 개선
- 다음글 [triton] Warp Specialization: 데이터 플로우 그래프 기반의 개선된 파티션 스케줄링 패스
댓글