[Loki] 캐시 최대 크기 초과 시 조기 중단으로 OOM 방지
PR 링크: grafana/loki#21277 상태: Merged | 변경: +756 / -549
들어가며
Loki의 태스크 결과 캐시는 모든 Arrow 레코드를 메모리에 버퍼링한 후, 파이프라인이 종료되면 인코딩과 압축을 수행하고, 그 결과가 설정된 최대 크기를 초과하면 캐시에 저장하지 않았다. 문제는 이 시점에서 이미 수백 GiB에 달하는 데이터가 메모리에 올라와 있어 워커의 OOM이 발생할 수 있다는 것이다.
핵심 코드 분석
Before: 전체 버퍼링 후 크기 확인
기존 cachingPipeline은 모든 레코드를 cached []arrow.RecordBatch 슬라이스에 모은 뒤, EOF 시점에 한꺼번에 인코딩하고 크기를 확인했다.
After: 증분 인코딩 + 즉시 크기 체크
새로운 구현은 recordEncoder를 도입하여 레코드가 들어올 때마다 즉시 인코딩하고, 누적 크기가 최대값을 초과하면 즉시 passthrough 모드로 전환한다:
type cachingPipeline struct {
inner Pipeline
cache cache.Cache
key string
maxSizeBytes uint64
compression string
hit bool
// For hit path
decoder *recordDecoder
// For miss path
passthrough bool
encoder *recordEncoder
}
Miss path에서 레코드를 읽을 때:
rec, err := p.inner.Read(ctx)
if err != nil {
if !errors.Is(err, EOF) || p.passthrough {
return nil, err
}
payload, commitErr := p.encoder.Commit()
// ... 캐시에 저장
}
벤치마크 결과
Per-record Snappy 압축이 전체 Snappy 압축보다 오히려 빠른 것으로 나타났다:
| 방식 | 인코딩 속도 | 디코딩 속도 |
|---|---|---|
| WholeSnappy | 1,726 MB/s | 4,694 MB/s |
| PerRecordSnappy | 2,150 MB/s | 4,958 MB/s |
왜 이게 좋은가
- OOM 방지: 대용량 응답이 캐시 최대 크기를 초과하면 즉시 중단하여 메모리 낭비를 방지한다.
- 메모리 사용량 감소: 전체 결과를 버퍼링하지 않고 증분 인코딩하므로 피크 메모리가 크게 줄어든다.
- 성능 향상: Per-record 압축이 전체 압축보다 빠르고 메모리 할당도 적다 (1.58GB vs 2.42GB B/op).
- Proto 확장:
physical.Cache.Compression필드를 추가하여 압축 방식을 설정으로 제어할 수 있게 했다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Grafana Loki] Bitmap.Slice에서 바이트 정렬 경계의 off-by-one 패닉 수정
- 현재글 : [Loki] 캐시 최대 크기 초과 시 조기 중단으로 OOM 방지
- 다음글 [Open WebUI] 비중국어 콘텐츠에 대한 불필요한 처리 스킵으로 스트리밍 성능 개선
댓글