본문으로 건너뛰기

[Loki] TSDB 풀에 전체 슬라이스를 올바르게 반환하여 메모리 할당 99.6% 감소

PR #20969 - perf(tsdb): return full slice to pool after use

들어가며

Grafana Loki의 TSDB 모듈에서 슬라이스 풀(pool)이 제대로 작동하지 않는 미묘한 버그가 있었습니다. defer로 풀에 슬라이스를 반환할 때, defer 선언 시점의 값이 캡처되어 빈 슬라이스가 반환되는 문제였습니다. 이로 인해 풀이 사실상 무용지물이 되어 매번 새로운 메모리 할당이 발생했습니다.

핵심 코드 분석

Before

chks := ChunkMetasPool.Get()
defer ChunkMetasPool.Put(chks)
// chks에 데이터가 추가됨...
// 하지만 defer는 선언 시점의 chks 값(빈 슬라이스)을 캡처

After

chks := ChunkMetasPool.Get()
defer func() { ChunkMetasPool.Put(chks) }()
// 클로저가 실행 시점의 chks 값(채워진 슬라이스)을 참조

이 변경은 4개 파일에 걸쳐 동일한 패턴으로 적용되었습니다.

벤치마크 결과

벤치마크 Before After 개선
GetChunkRefs sec/op 860.3us 693.8us -19.35%
GetChunkRefs B/op 1374.7KB 5.1KB -99.63%
GetChunkRefs allocs/op 27 18 -33.33%
Volume sec/op 280.0us 275.6us -1.56%
geomean B/op 630.6KB 248.6KB -60.58%

왜 이게 좋은가

  1. Go의 defer 동작 이해: defer f(x)는 선언 시점에 x를 평가하지만, defer func() { f(x) }()는 실행 시점에 x를 평가합니다. 이 차이가 풀의 효과를 결정했습니다.
  2. 메모리 할당 99.6% 감소: GetChunkRefs에서 1374KB에서 5KB로 메모리 사용이 극적으로 줄었습니다.
  3. 단 한 줄 변경의 위력: 각 수정은 defer Pool.Put(x) -> defer func() { Pool.Put(x) }() 한 줄 변경이지만, 성능 영향은 매우 큽니다.

참고 자료

댓글

관련 포스트

PR Analysis 의 다른글