[Loki] 인덱스 빌더에서 오브젝트 다운로드 시 슬라이스 사전 할당으로 메모리 효율화
PR 링크: grafana/loki#20647 상태: Merged | 변경: +60 / -12
들어가며
Loki의 인덱스 빌더가 오브젝트 스토리지에서 데이터 오브젝트를 다운로드할 때, io.ReadAll을 사용하여 바이트 버퍼를 할당하고 있었습니다. io.ReadAll은 512바이트부터 시작하여 append로 슬라이스를 점진적으로 확장하는데, 대용량 오브젝트에서는 수 차례의 재할당과 복사가 발생합니다. 이 PR은 오브젝트 크기를 먼저 확인하여 정확한 크기로 사전 할당합니다.
핵심 코드 분석
Before: io.ReadAll의 점진적 확장
func downloadWorker(ctx context.Context, ...) {
objectReader, err := objectBucket.Get(ctx, event.ObjectPath)
if err != nil { ... }
object, err := io.ReadAll(objectReader)
_ = objectReader.Close()
// ...
}
io.ReadAll은 내부적으로 512바이트 버퍼에서 시작하여, 가득 차면 2배씩 확장합니다. 10MB 오브젝트를 읽으려면 약 15번의 재할당(512 -> 1K -> 2K -> ... -> 16MB)이 필요합니다.
After: Attributes 기반 사전 할당
func downloadObject(ctx context.Context, bucket objstore.Bucket, path string) ([]byte, error) {
reader, err := bucket.Get(ctx, path)
if err != nil {
return nil, fmt.Errorf("failed to fetch object from storage: %w", err)
}
defer reader.Close()
// 오브젝트 크기를 먼저 확인하여 정확한 크기로 할당
attrs, err := bucket.Attributes(ctx, path)
if err == nil && attrs.Size > 0 {
buf := make([]byte, attrs.Size)
_, err = io.ReadFull(reader, buf)
if err != nil {
return nil, fmt.Errorf("failed to read object: %w", err)
}
return buf, nil
}
// Attributes 실패 시 기존 방식으로 폴백
object, err := io.ReadAll(reader)
if err != nil {
return nil, fmt.Errorf("failed to read object: %w", err)
}
return object, nil
}
왜 이게 좋은가
1. 메모리 재할당 제거
make([]byte, attrs.Size)는 정확한 크기를 한 번에 할당합니다. 10MB 오브젝트 기준으로 약 15번의 재할당과 복사가 0번으로 줄어듭니다. 각 재할당마다 기존 데이터를 새 버퍼로 복사해야 하므로 총 복사량도 크게 감소합니다.
2. GC 압력 감소
점진적 확장에서 버려지는 중간 버퍼들(512B, 1KB, 2KB, ...)은 모두 GC 대상입니다. 사전 할당하면 이 중간 쓰레기가 발생하지 않으므로 Go의 GC 부하가 줄어듭니다.
3. 안전한 폴백 전략
Attributes 호출이 실패하면 기존의 io.ReadAll 방식으로 폴백합니다. 이로써 모든 오브젝트 스토리지 구현체와의 호환성을 유지합니다.
4. 프로파일링 기반 최적화
PR에 첨부된 프로파일링 결과에서 io.ReadAll의 append 관련 할당이 상당한 비중을 차지하는 것을 확인할 수 있으며, 이 최적화가 실측 데이터에 근거한 것임을 보여줍니다.
참고 자료
- Go io.ReadAll 소스코드 — 512바이트 초기 할당
- Go Slice Internals — 슬라이스 확장 전략과 비용
관련 포스트
PR Analysis 의 다른글
- 이전글 [Triton] AMD PartitionedSharedEncodingAttr 도입 — shared memory 파티션 충돌 감소
- 현재글 : [Loki] 인덱스 빌더에서 오브젝트 다운로드 시 슬라이스 사전 할당으로 메모리 효율화
- 다음글 [Triton] AMD TDM AsyncWait을 UpdateAsyncWaitCount에서 지원
댓글