[Grafana Loki] dataobj pageReader의 메모리 할당을 Reclaim과 Bitmap 직접 전달로 최적화
PR 링크: grafana/loki#20496 상태: Merged | 변경: +29 / -19
들어가며
Grafana Loki의 dataobj 패키지에서 pageReader는 데이터 페이지를 읽는 핵심 컴포넌트입니다. 기존 구현에서 두 가지 할당 문제가 있었습니다. 첫째, 매 read() 호출 시 alloc.Reset()을 사용하면 NULL만 포함된 읽기에서 값 메모리가 조기 해제되어 다음 읽기에서 재할당이 발생했습니다. 둘째, presence 값 디코딩에서 columnar.Array 인터페이스를 통해 반환하면서 불필요한 래핑 할당이 생겼습니다.
핵심 코드 분석
Before: Reset으로 조기 해제 + 간접 비트맵 반환
func (pr *pageReader) read(v []Value) (n int, err error) {
pr.alloc.Reset() // 모든 할당 메모리를 해제 - NULL 전용 읽기에서 문제
// presence 비트맵을 columnar.Array 인터페이스로 반환
presenceArr, err := pr.presenceDec.Decode(&pr.alloc, len(v))
presenceBuf := presenceArr.(*columnar.Bool)
presenceValues := presenceBuf.Values()
presentCount := presenceValues.SetCount()
for i := range count {
if presenceBuf.Get(i) { ... }
}
}
After: Reclaim으로 안전한 메모리 관리 + 비트맵 직접 전달
func (pr *pageReader) read(v []Value) (n int, err error) {
pr.alloc.Reclaim() // 사용하지 않는 메모리만 회수, 값 메모리 보존
// 비트맵을 직접 생성하여 DecodeTo에 전달
bm := memory.MakeBitmap(&pr.alloc, len(v))
err = pr.presenceDec.DecodeTo(&bm, len(v)) // 인터페이스 래핑 불필요
presentCount := bm.SetCount()
for i := range count {
if bm.Get(i) { ... }
}
}
// Reset은 pageReader.Reset()에서 한 번만 호출
func (pr *pageReader) Reset(page Page, ...) {
pr.alloc.Reset() // 페이지 변경 시에만 전체 리셋
}
왜 이게 좋은가
- 조기 해제 방지:
Reclaim()은Reset()과 달리 사용 중인 메모리를 보존하므로, NULL 전용 읽기 후에도 값 버퍼가 유지되어 다음 읽기에서 재할당이 필요 없다. - 인터페이스 래핑 제거:
DecodeTo가 비트맵 포인터를 직접 받아columnar.Array인터페이스 변환과 타입 어설션 비용을 제거했다. - 할당 위치 최적화: 전체 메모리 리셋을 페이지 변경 시점(
Reset)으로 이동하여, 같은 페이지 내 연속 읽기에서 할당을 최소화했다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [Loki] Bitmap 디코더 최적화: 처리량 93.5% 개선
- 현재글 : [Grafana Loki] dataobj pageReader의 메모리 할당을 Reclaim과 Bitmap 직접 전달로 최적화
- 다음글 [Loki] 부모-자식 메모리 할당자 도입으로 계층적 메모리 수명 관리
댓글