[DevOps] 2편 : Trouble Shooting – Memory Leak

안녕하세요? 정리하는 개발자 워니즈 입니다. 이번시간에는 필자가 속한 프로젝트에서 했던 트러블 슈팅에 대해서 정리를 해보려고 합니다. 이번 트러블 슈팅을 준비하면서 나름 배운것도 많고, 실제로 해결까지 수행했기에 많은 부분에 있어서 기록을 해두면 좋을 것이라고 생각했습니다.

이번 포스팅은 지난번 GC 이론과 이어지는 내용으로 반드시 GC 튜닝을 해야하는가? 에 대한 필자의 답변은 사실 가장 본이 최선이다라는 생각으로 모든값들을 최대한 조정하지 않으려고 합니다.

1. 모니터링 수행

그런데 어느날인가, 필자가 운영하는 서비스의 어플리케이션의 이상을 감지하기 시작했습니다. 특정 어플리케이션이 지속적으로 pod의 재시작이 감지가 되었습니다.

처음에는 일시적인 현상일거라 생각하고 넘어갔었는데, 신기하게도 주기성을 갖고 재시작을 하고 있었습니다. 위에서 보시는것처럼 2개의 pod가 일정 주기를 갖고 재시작을 하고 있었습니다. 이러한 상황에서 볼수 있는 것은 CPU, Memory를 먼저 체크하기로 했습니다.

메모리 확인

memory 사용률을 보니, 시간이 지날수록 지속적으로 메모리가 증가하기만 하고 소모(GC)가 되지 않고 있는것으로 판단했습니다. 점점 치닫다가 도저히 수행할 수 없는 상황에 다다라서는 재시작을 하여 메모리를 소모하는 구조로 되어있었고, 이부분은 k8s에서 운영을 안했으면 계속해서 메모리 문제로 운영자가 투입을 해야될 상황이였습니다.

JVM 사용 현황 확인

위에서 보니 명확하게 문제가 식별되고 있었습니다. 좌측 그림이 OLD영역에 해당하는 내용인데, GC가 일어날 때마다 메모리가 감소는 하지만 그 주기가 시간이 지날수록 점점 잦아지고 있는 것을 식별할 수 있습니다.

그리고 가장 우측에 Suvivor영역은 GC가 일어나더라도 소모가 되지 않고, 계속해서 객체가 쌓여나가는 것을 볼 수 있었습니다. 결국 어디선가 누수가 발생을 한다는 것으로 생각할 수 있고, 이부분을 찾기로 했습니다.

2. 모니터링 분석

메모리 누수가 확실히 있음을 확인했으니, 어느부분에서 누수가 발생을 하는지 체크하기로했습니다. 필자는 다음의 순서로 분석을 수행하기로했습니다. ( Heap Dump 분석, GC Log 분석)

Heap Dump 분석

필자는 IBM HeapAnalyzer를 사용하여 분석을 수행했습니다. Heap Dump뜨는 것은 굉장히 간단합니다.

$ jmap -dump:format=b,file=heapdump.hprof 84544

jmap 명령어를 사용하면 현재 수행중인 어플리케이션의 heap dump를 획득 할 수 있습니다. 이 덤프파일을 그대로 import 해주면 위와 같이 정확하게 분석을 수행해 줍니다.

GC Log 분석

GC 로그 분석은 위의 JVM 사용 현황에서 어느정도 분석이 되었지만 좀더 자세히 수행을 하기 위해서 GC 로그를 획득하여 분석하기로 했습니다.

다행히도 필자가 운영중인 어플리케이션으 GC로그를 서비스중에 남기고 있기에 바로 획득이 가능했습니다. 별도의 명령어를 쓰지 않고 해당 파일을 improt 해서 분석하기로 했습니다.

위에서 보는것처럼 pod가 시작과 동시에 메모리가 지속적으로 상승을 하고 GC가 수행되는 횟수가 엄청 잦아지고 있습니다. 규칙적으로 수행하는 것이 아니라 메모리 사용현황에 문제가 발생을 했기 떄문에 위와 같은 현상이 발생을 하는것입니다.

3. GC 분석 결과

결론적으로는 redisson library가 원인이라는 것을 확인했고, 자세한 개발코드에 대해서 분석이 된것은 아니지만 실제로 redisson github에 이슈로 제기된것이 있는 것을 확인했습니다.

https://github.com/redisson/redisson/issues/2539

메모리 누수에 따라 지속적으로 버전이 업된것을 확인했고, 이부분에 있어서 redisson library를 업그레이드를 하는것이 최선인 것을 확인했습니다.

필자가 직접 redisson library를 수정하고 개발 환경에 배포를 하고 약 7일정도의 시간을 확인했습니다. 같은 환경에서 발생하던 메모리 누수가 해소가 되어 Major GC인 OLD영역도, Minor GC인 Suvivor 영역도 더이상 쌓이지 않는것을 볼 수 있습니다.

  • *redisson library update*
  • 현재 사용버전 :3.12.0

  • 업데이트 버전 : 3.14.1( 현재 3.16.8 까지 있으나 버전을 올렸을때 참조 오류가 있어 올릴수 있는 최대로 올려봤습니다.)

  • 메모리 누수 현상이 사라짐.

4. 트러블 슈팅을 통해…

이번 트러블 같은 경우는 다행히 GC에 따른 stop-the-world 가 크게 발생해서 문제가 되는 케이스는 아니였습니다.

대게 GC를 튜닝하는 조건은 다음과 같은것으로 확인을 했습니다.

  • GC 수행시간이 1-3초, 길게는 10초가 넘는 시간이라면 튜닝을 고려
  • GC 수행시간이 다음의 수준이라면 튜닝은 고려할 필요가 없음.
    • Minor GC의처리시간이빠르다(50ms내외).
    • Minor GC 주기가빈번하지않다(10초내외).
    • Full GC의처리시간이빠르다(보통1초이내).
    • Full GC 주기가빈번하지않다(10분에 1회).

위의 값은 절대기준은 아니고 당연하게도 서비스의 상황에 따라서 달라질 수 있는 값들입니다.

메모리 크기에 따라 GC의 발생횟수, 수행시간에도 영향을 주니 잘 따져가며 튜닝을 진행해야 합니다.

  • 메모리크기가크면
    • GC 발생횟수는줄어든다.
    • GC 수행시간은길어진다.
  • 메모리크기가작으면
    • GC 수행시간은적어진다.
    • GC 발생횟수는증가한다.

사실 필자도 이번 트러블 슈팅을 GC 방식의 문제라고 처음에는 생각을 하여, GC 방식에 대한 변경도 고려했었는데 여러 조건에 걸쳐서 시험을 수행해봤지만 위의 증상과 동일하게 Survivor 영역의 메모리가 지속적으로 찼습니다.

5. 마치며…

위의 트러블 슈팅을 통해, 힙덤프, GC로그, 쓰레드 덤프까지 모두 확인을 해보면서 다양한 방식으로 접근을 수행해봤었습니다. GC의 원리와 방식도 이해를 했고, 튜닝은 어떤식으로 접근을 해야하는지도 알아봤습니다.

위의 상황을 확인하면서 다행히도 공용적으로 사용하는 라이브러리의 문제점임을 확인하고 개발팀에 공유해줬을 때 개발팀에서도 굉장히 좋아했었씁니다.

현재는 GC 튜닝의 단계만큼 어플리케이션들이 모니터링상으로는 특이사항을 식별하지 못했지만 좀더 면밀히 살펴보아서 개선할 수 있는 부분을 찾아가도록 해야겠습니다.

6. 참고

Garbage Collection 튜닝

Java GC 튜닝

GC Tuning: In Practice

https://brunch.co.kr/@alden/45

One Reply to “[DevOps] 2편 : Trouble Shooting – Memory Leak”

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다