티스토리 뷰
어느 날 갑자기 멈춰버린 서버
4월 중순이었나? 첫 입사 후 3개월 동안 틈틈이 리팩터링 한 코드를 배포한 이후 2일 간격으로 회사의 정상적으로 작동하던 서버가 죽어버렸다. 작은 스타트업에 유일한 신입 백엔드 개발자로 재직 중인 상황에 실무에서 처음 겪은 상황에 당황스러웠다.
로그를 확인해보니MySQL로 넘어가는 과정에서 DeadLock이라는 단어가 보였고, 이 때문이구나 라는 생각으로 이어졌다.
데드락은 두 가지 원인이 있었는데 하나는 이전에 데이터 무결성을 지키기 위해 트랜잭션 범위를 수정했었는데, 수정한 트랜잭션 범위가 넓어서 였고. 두 번째는 클라이언트 단에서의 중복요청이 발생해서 생겨난 것이다.
두 가지 원인중 하나가 클라이언트쪽 영향이었고. DeadLock이 원인이라면 이전에도 이와같이 서버가 꺼졌어야 하지 않을까 라는 생각으로 이어졌다. 문제파악을 다시 하기위해 첫번째 원인에 코드를 문제 이전 상태로 롤백한 후 서버가 꺼지는 주기를 체크해보기로 했다.
이전 상태로 롤백한 후에도 서버가 죽는 주기에는 변함이 없었고, 정확한 원인 파악을 위해 메모리 분석을 해보기로 했다.
JVM 힙 메모리 분석하기
힙 덤프 분석
첫 번째 할 일은 현재 서비스중인 서버의 힙 덤프 파일을 만드는 것이다. JDK 에서 제공해주는 jmap 을 활용하면 되는데, 현재 서비스중인 프로세스의 ID (PID) 를 확인하고 해당 PID를 jmap 을 통해 파일로 만들면 된다. 그렇게 만들어진 파일은 '.hprof' 확장자를 가진다. 해당 파일을 로컬 PC로 가져온 후, 해당 파일은 VisualVM을 통해 GUI 로 손쉽게 확인할 수 있다.
[ VisualVM ]
https://visualvm.github.io/
분석
덤프 파일을 VisualVM으로 확인 했는데 막막했다. 힙 메모리를 분석 해보거나, 아직 GC등 JVM 성능과 관련된 내용을 깊게 공부해본적 없었기 때문인데, 어쩔 수 없이 개인적으로 의심스러운 부분을 기준으로 세워 확인 해볼 수 밖에 없었다.
덤프 파일에서 확인할 수 있었던 것은 ConcurrentHashMap 과 PartTreeJpaQuery 가 비정상적으로 많은 용량을 차지하고 있다는 것이다. 지금까지는 당연히 내 코드의 문제라고만 생각했지만 위 키워드 기준으로 비슷한 이슈가 없었는지 인터넷에 찾아보기 시작했다.
원인
서칭 결과 비슷한 상황을 겪는 아래 페이지를 확인할 수 있었다.
- https://github.com/spring-projects/spring-boot/issues/19546
Memory leak in PartTreeJpaQuery after upgrade from 2.1.9 to 2.2.2 · Issue #19546 · spring-projects/spring-boot
We bumped spring boot from 2.1.9 to 2.2.2 not so long ago, and right after that we started getting OOMs from time to time ( 7-8 times a day). After downloading the heap-dump a few times through the...
github.com
위 페이지에서 내용은 Spring Boot 2.2 으로 업데이트 한 이후 PartTreeJpaQuery 와 ConcurrentHashMap 항목에서 메모리 누수가 발생한다는 내용이었다. 정확히 우리와 같은 상황이었던 것이다. 다만, 같은 스프링 버전을 내가 입사하기 이전부터 써왔음에도 문제가 발생하지 않았던 점 때문에 확신을 할 수 없었다.
위 이슈의 다른 글
- https://github.com/spring-projects/spring-data-jpa/pull/402
- https://github.com/spring-projects/spring-data-jpa/issues/1951
메모리 누수 해결
더이상 확인해볼 요소가 생각나지도 않았거니와, 거의 90%는 위 내용이 문제의 원인일 것이라고 확신한 상황이었기 때문에, 문제 해결방법을 스프링 부트 버전을 업데이트하는 것으로 결정했다.
스프링 부트 2.3.3 버전으로 버전을 업데이트 했고, 약 2주동안 서버 모니터링을 하고 문제가 생기지 않았고 한 달정도 지난 현재도 문제가 생기지 않고 있다. 문제가 해결된 것이다.
느낀점
문제 해결과정에서 처음에는 DeadLock이 원인이라고 오인했다. 글에서는 몇 문장에 끝났지만 시간적으로는 꾀 많은 시간을 낭비했다. 문제 원인 파악을 정확히 하지 못했기 때문이다. 문제 파악이 얼마나 중요한지 알 수 있게 해주는 대목이다.
'BackEnd' 카테고리의 다른 글
Netty VS Tomcat 비교 분석하기 (0) | 2022.11.08 |
---|---|
nGrinder를 활용한 부하 테스트 (2) | 2022.10.05 |
[MSA] 벌크헤드 패턴( Bulkhead Pattern ) (0) | 2022.09.18 |
Circuit breaker Pattern ( 회로 차단기 패턴 ) (0) | 2022.09.03 |
Fallback Pattern ( 대체 패턴 ) (0) | 2022.09.01 |
- Total
- Today
- Yesterday
- 동적 디스패치
- Clean Architecture
- 수평 분할
- 컴포짓 패턴
- 동적 타입 언어
- 클린 아키텍처
- 수직 분할
- RestAssured
- Memory Fragmentation
- 육각형 아키텍처
- OOP
- ATDD
- Session
- SpringBoot 2.2
- 뾰족함
- pool
- 메모리 단편화
- pass by reference
- Sticky Session
- 정적 타입 언어
- java
- 객체 풀
- 외부 단편화
- 메모리 파편화
- pass by value
- Object Pool
- multimap
- 세션 불일치
- 내부 단편화
- 장애 해결기
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |