대량 메시지 파이프라인 최적화 해보기 2편(Memory Leak)

Jeerryy·2025년 4월 24일
0

Optimization

목록 보기
2/2
post-thumbnail

Rate Limit을 적용하면 모든 것이 해결될 줄 알았던 어리석었던 나에게 경각심을 주는 2편입니다.

1편의 최적화 과정에서 Rate Limit 적용과 함께 쿼리 최적화도 같이 진행 됐습니다.

optimize-queries 문서를 참고하여 진행했습니다.

기존에는 아래처럼 contain 함수를 이용하여 pubsub을 통해 인입된 serialNo만 필터링하도록 하여 전체 조회에 비해 적은 시간을 소요되도록 의도하려는 것과는 상반된 쿼리 실행 결과를 보여주었습니다.

contain function을 이용한 쿼리(84ms)
contain function을 이용한 쿼리 조회(84ms)

전체 쿼리 조회(40ms)
전체 쿼리 조회(40ms)

이를 통해 InfluxDB의 큐가 병목되어 CPU를 지속적으로 점유하는 현상을 감소하도록 했습니다

만, InfluxDB는 안정적인 반면에 쿼리의 거대한 Response Size로 인해 Pipeline Server의 Heap Memory에 누적되어 GC가 되지 못하는 Side-effect가 발생했고 Pod Memory Usage가 Memory Limit를 초과하여 지속적으로 재시작되는 참사가 일어났습니다.

크롬에서 V8 엔진을 디버깅할 수 있는 페이지(chrome://inspect)를 제공해주었고 페이지를 이용하여 디버깅을 시작했습니다.

페이지를 통해서 힙 스냅샷을 찍어본 결과 문자열 쪽에서 엄청난 누수가 발생하고 있었습니다.
문자열 메모리 누수
문자열 쪽 메모리 누수가 심한 편

할당 샘플링 프로파일을 통해 메모리 누수가 발생하는 지점을 확인해본 결과 emit 쪽에서 메모리 누수가 발생하고 있었고 서버 자체적으로도 event-emitter를 사용하고 있었기에 관련 코드를 걷어내보았지만 상황은 크게 변하지 않았습니다.

InfluxDB에서 제공하는 NPM 내부적으로 Response Data 모두 event-emitter로 전가하고 있었고 위의 대량의 Response Data를 견디지 못했던 것이죠.

메모리 누수 상세
emit 과 관련된 부분이 메모리 누수의 원인으로 보임.

npm 전체를 뜯어고칠 노릇은 아니었기 아니었기 때문에 차라리 npm을 걷어내고 axios를 이용하여 InfluxDB가 제공하는 REST API를 사용하는 것으로 결정했습니다.

추가로 contain function이 아닌 equal 와 or operator 조합하여 쿼리를 작성했을 때 tags를 활용한 indexing이 정상적으로 사용하는 것을 활용하여 쿼리를 추가로 수정했습니다.

or와 equal operator를 이용한 쿼리(12ms)
or와 equal operator를 이용한 쿼리(12ms)

이러면 Event Emitter를 걷어낸 의미가 없지 않냐고 할 순 있지만 잠재적으로 대형 쿼리 데이터를 리턴해야할 때 메모리 누수를 야기할 수 있기 때문에 무의미하지 않다고 생각하기 때문에 Rollback 하지 않고 강행했습니다.

결론

메모리 사용량은 크게 변화가 없다.
단기적으로 확인했을때는 메모리 누수를 확인할 수 없었고 그렇다고 메모리 사용량이 감소하지도 않았습니다.

하지만 VM을 보면 상황이 달랐는데요.
VM을 보니 변했구나.

gzip 형식으로 압축하여 요청을 보냈기 때문에 네트워크 트래픽이 약 1927% 감소했고 디스크 처리량이 약 151% 상승한 모습을 보이고 있습니다.

결론적으로는 동일한 메모리 사용량 대비 처리량이 증가했다고 볼 수 있겠습니다.

지표상으로 본다면 지난 최적화는 VM의 CPU 관점에서 최적화를 진행했고 이번 최적화에서는 Pipeline Server Memory 관점에서 진행하어 지난 최적화 이후로도 큰 개선이 이루전 것으로 보입니다.

profile
다양한 경험을 해보고자 하는 Backend-Engineer.

0개의 댓글