2024. 7. 31. 14:32ㆍ프로젝트_트러블슈팅
프로젝트를 개발 서버에 배포하고 테스트 진행중 웹이 멈추는 현상이 발생했다.
바로 해당 서버의 로그를 확인해보니 원인은 GC Pause Full 가비지 컬렉터의 메모리 문제였다.
일단은 서버복구가 급선무였고 마침 이전 프로젝트에서도 Java 서버에 heap out 이 된 경험이 있어서 root.xml에 가서 메모리를 늘려주고 기존에 멈춰버린 프로젝트를 직접 kill 하고 재실행 해줬다.
( kill을 해줬는데 한번에 안죽어서 당황했다. 이런 상황에선 kill 하고나서 한번더 ps -ef로 다시 확인해보자!!)
하지만 이 상태로는 다시 GC의 메모리 문제가 생기는 것은 시간문제였기 때문에 소스 수정에 들어갔다.
GC에 문제가 생길만한 부분은 불필요한 변수 또는 반복적인 객체 선언 그리고 데이터를 제대로 clean이나 destroy 하지 않은 것이라 생각해서 소스 수정에 들어갔다.
1. 불필요한 변수 삭제
: 일단 소스를 뒤져본 결과 선언만 되고 사용하지 않은 변수는 없었고 찾지 못한 곳에 있다고 하더라도 영향은 미비하다고 판단했다.
(=> 실패)
2. 반복문 안에 들어간 객체선언 제거
: 반복문에 선언부분이 들어간 부분은 없었다.
(=> 실패)
3. 데이터 Clean 로직 추가
: list나 Map 등 몇몇 부분에서 사용 후 clean을 안 해준 코드가 있었다. 일단 변수 안에 데이터를 모두 clean 해줬지만 들어갈 데이터가 대용량이 아니었고 자주 호출되는 메소드도 아니기 때문에 영향도는 작을 거라 예상했다.
(=> 영향도 적음 )
상황이 좀 급했기 때문에 모든 소스를 다 찾아볼 수는 없었다. 그래서 호출 비중이 높거나 대용량 데이터를 다루는 메소드로 범위를 좁혀서 면밀히 보는 걸로 디버깅 방식을 바꿨다.
이 방식대로라면 가장 우선해서 확인해야될 메소드는 RAG와 LLM을 호출하는 부분이었다. 이러한 메소드들은 대용량의 텍스트를 받아오기 때문에 충분히 메모리 문제를 일으킬 수 있을 것 같았다.
확인 결과 LLM은 변수 사용을 최소화하고 메모리를 고려해서 작업한 흔적이 보였다. ( 이게 고급개발자의 코드다 )
하지만 RAG 관련 메소드는 그렇지 않았다. ( 이건 내가.... 작업한 코드... )
4. 변수 사용 최소화
내가 작성한 코드에는 나름 유지보수하기 좋게 한답시고 변수에 각 데이터를 넣어주고 return 시켜줬다. 컨트롤러에서 object 타입 데이터를 받을 때도 일일이 사용할 데이터를 변수에 담아주고 사용하는 식으로 작성 되어있었다.
다른 메소드 였다면 큰 영향을 주지 않았겠지만 여기서 다루는 데이터는 파라미터로 받은 Object에는 40가지 key를 가지고 있고 그중 5가지는 List 형식 또 7가지는 다시 Object 형식이었다. 또 return해주는 데이터도 RAG에서 나온 대용량 텍스트였기 때문에 내가 한 방식은 충분히 문제를 일으킬 가능성이 있어 보였다.
( 데이터를 보낼 때부터 정재하도록 하고 싶었으나 기존에 사용되던 솔루션 코드를 가져다가 수정하는 초단기 프로젝트 였기 때문에 함부로 데이터를 배제할 수가 없었다. 한번 했다가 나중에 별별 버그가 다 발생했기에... )
일단 가장 먼저 한것은 Mono 타입으로 받은 RAG 데이터(FastAPI 기반 다른 서버에서 REST방식으로 받아옴)를 변수에 저장하고 그 변수를 return해주는게 아니라 바로 return 하도록 바꿨다.
( 애초에 이렇게 작업을 한것이 log 때문이었는데 WebClient의 doOnError와 doOnNext라는 기능을 알게되서 수정할 수 있었다. )
return client.post()
.bodyValue(requestValue)
.retrieve()
.bodyToMono(new ParameterizedTypeReference<List<Box>>() {})
.doOnError(throwable -> log.error("err : {}", throwable.getMessage()))
.log()
.doOnNext(result -> log.debug("rag response data: {}", result));
(전체 코드는 보안상 공개할 수가 없어 수정한 return 부분만 남겨둔다.)
그 외에도 컨트롤러에서 서비스단을 호출할때 필요한 파라미터를 똑같이 변수에 옮겨담지 않고 바로 넣어주고 서비스단에서 mapper 호출 시에도 마찬가지로 변경해줬다.
(=> 성공 )
Apache JMeter로 부하테스트 결과 기존 꺼는 마찬가지로 GC에서 문제가 생기는 반면 수정한 코드는 기존 코드가 GC문제가 발생한 지점보다 더 호출양을 늘려도 GC 메모리에 문제가 생기지 않는 것을 확인했다.
이후 테스트 진행시 로그 모니터링 중에도 GC Pause Young은 발생했지만 Full 까지 가지는 않았다.
( ㄴ> 여기서 또다른 문제 발견 : GC Pause Young 의 시간이 0.4초나 걸렸다. 확인한 10개의 로그 중 하나만 0.1초 대이고 나머진 밀리초 단위로 나왔지만 문제가 생길 여지가 있으니 GC 방식을 변경하는 등의 고민이 필요해보인다.
나중에 가비지 컬렉터도 공부해서 포스팅 하겠다.)
>> 오늘의 트러블 슈팅 결론 :
변수를 선언해서 사용할 때는 신중하게 하고 개발시에 메모리를 고려해서 개발하자!!
'프로젝트_트러블슈팅' 카테고리의 다른 글
[PersonaAI - 비공개 프로젝트 d-2] 파일다운로드 기능 오작동 (0) | 2024.07.31 |
---|---|
[PersonaAI - 비공개 프로젝트 d] myBatis useGeneratedKeys 안될때 (2) | 2024.07.25 |
[PersonaAI - 비공개 프로젝트b-2] Java 크롤링 (0) | 2024.03.12 |
[PersonaAI - 비공개 프로젝트b] try-catch문 에러 catch 실패 (0) | 2024.02.23 |
[PersonaAI - 비공개 프로젝트b] node.js 크롤링 (0) | 2024.02.23 |