[Grafana Loki] 스케줄러 Peer 연결 미종료로 인한 메모리 누수 수정
PR 링크: grafana/loki#20268 상태: Merged | 변경: +9 / -6
들어가며
Grafana Loki의 쿼리 엔진에서 threadJob은 하나 이상의 streamSink를 가지며, 각 streamSink는 다른 워커와의 피어 연결입니다. streamSink가 종료될 때 피어의 Stop() 메서드는 호출했지만, 실제 네트워크 연결은 닫지 않았습니다. 이로 인해 반대편 워커의 Serve() 메서드가 영원히 반환되지 않아 고루틴과 메모리가 계속 누적되었습니다.
핵심 코드 분석
Before: Serve()에서 연결 종료 없음
func (p *Peer) Serve(ctx context.Context) error {
p.lazyInit()
// 연결 종료 로직 없음 - Peer에 Close 메서드도 없음
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return p.recvMessages(ctx) })
// ...
}
func (p *Peer) recvMessages(ctx context.Context) error {
for {
frame, err := p.Conn.Recv(ctx)
if err != nil && ctx.Err() != nil {
return nil
} else if err != nil {
return fmt.Errorf("recv: %w", err) // 연결이 안 닫혀서 여기 도달 불가
}
}
}
After: defer로 연결 종료 보장
func (p *Peer) Serve(ctx context.Context) error {
p.lazyInit()
// Peer에 명시적 Close 메서드가 없으므로 Serve에서 연결 종료
defer p.Conn.Close()
g, ctx := errgroup.WithContext(ctx)
g.Go(func() error { return p.recvMessages(ctx) })
// ...
}
func (p *Peer) recvMessages(ctx context.Context) error {
for {
frame, err := p.Conn.Recv(ctx)
if err != nil {
if ctx.Err() != nil {
return nil
}
return err // 에러 래핑 제거로 간결화
}
}
}
왜 이게 좋은가
- 근본 원인 해결: 연결이 닫히지 않으면
Recv()가 블로킹 상태로 유지되어 고루틴이 해제되지 않는다.defer p.Conn.Close()로 Serve 종료 시 연결을 반드시 닫는다. - 최소한의 변경:
defer한 줄과 에러 처리 정리만으로 메모리 누수를 해결했다. - 에러 처리 개선:
ctx.Err()체크를 에러 존재 여부 확인 내부로 이동하여, 에러 없이 컨텍스트가 취소된 경우의 불필요한 체크를 제거했다.
참고 자료
관련 포스트
PR Analysis 의 다른글
- 이전글 [triton] CGAEncodingAttr::getDefault를 get1CTALayout/get1DLayout로 분리하여 multi-CTA 지원
- 현재글 : [Grafana Loki] 스케줄러 Peer 연결 미종료로 인한 메모리 누수 수정
- 다음글 [Ray Data] StreamingRepartition과 MapBatches 퓨전 규칙 개선
댓글