이번에 회사에서 직면했던 Querydsl StackOverflow에 관한 내용을 공유하고자 글을 남깁니다.
오늘도 어김없이 문제는 Slack 메시지로부터 시작합니다.
메시지를 받자마자 어라? 라는 반응을 할 수밖에 없었습니다.
StackOverflow??? 서버에 재귀식으로 구현된 코드가 있었던가???
여러분들 모두 잘 알고 계시겠지만 StackOverflow란 지정한 스택 메모리 사이즈보다 더 많은 스택 메모리를 사용하게 되어 에러가 발생하는 상황입니다.
메서드가 호출되면 호출 스택 에 새 Stack frame
이 생성됩니다 . 이 Stack frame
은 호출된 메서드의 매개 변수, 로컬 변수 및 메서드의 반환 주소, 즉 호출된 메서드가 반환된 후 메서드 실행이 계속되어야 하는 지점을 포함합니다.
Stack frame
생성은 중첩된 메서드 내에서 발견된 메서드 호출의 끝에 도달할 때까지 계속됩니다.
이 과정에서 JVM이 새 스택 프레임을 생성할 공간이 없는 상황이 발생하면 StackOverflowError 가 발생 합니다.
JVM이 이 상황에 직면하는 가장 일반적인 원인은 종료되지 않은/무한 재귀 입니다.
원인 발생지점은 어렵지 않게 찾을 수 있었습니다.
JPA를 사용하며 동적쿼리 작성을 용이하기 위해 사용한 QueryDsl이 문제지점이였습니다.
Stacktrace를 보자마자 아...
이거 Querydsl이 재귀식으로 구현돼있는 부분이 있나보구나 했습니다..
코드를 까보죠
해당하는 각 JPAMapAccessVisitor
, OperationImpl
, ReplaceVisitor
를 보겠습니다.
코드를 까서보니 조건 Expression
을 만드는 과정에서 재귀식으로 구현돼있는게 맞았습니다.
위 JPAMapAccessVisitor
27,58 라인, OperationImpl
88 라인, ReplaceVisitor
51,161 라인을 계속 돌며 Stack frame을 열심히 차곡차곡 쌓아갔던 겁니다.
우리가 Querydsl에서 익히 쓰는 BooleanBuilder를 사용할 경우 위와 같은 로직을 타게됩니다.
booleanbuilder안에 조건을 많이 넣게 될경우 재귀식으로 해당 조건들을 구현하는 과정에서 StackOverflow가 터지게 된거죠.
위 슬랙 메세지가 온경우에는 조건이 2000개가 넘었습니다.
원인 지점은 QueryDsl이였기 때문에 저는 조건이 많이 생길 수 있는 로직에서는 JPQL을 사용하는 것으로 결론지었습니다. 이게 맞는 해결방법인지는 모르겠지만 오랜만에 보는 StackOverflowError라서 반갑기도 했습니다 ㅎ...
한편으로는 Querydsl도 애플리케이션 입장에서보면 의존성이 하나 더 생기는 것이기때문에 장애발생 가능지점이 하나 늘어나는 거구나 싶긴합니다.
관련된 Github issue와 대화가 있어 Reference에 남겨놓도록 하겠습니다.
Querydsl사용할때 StackOverflow 다들 조심하십쇼!
위 본문 내용중 정확하지 않은 내용이 포함돼 있을 수 있습니다.
저는 1년차 백엔드 개발자로 스스로 굉장히 부족한 사람이라는 점을 인지하고 있는지라
제가 적은 정보가 정확하지 않을까 걱정하고 있습니다.
혹여 제 정보가 잘못 됐을 수 있으니 단지 참고용으로만 봐주시고 관련된 내용을 한번 직접 알아보시는 걸 추천합니다.
혹여 잘못된 내용이 있거나 말씀해주시고 싶은 부분이 있으시다면 부담없이 적어주세요!
수용하고 개선하기 위해 노력하겠습니다!!!
https://github.com/querydsl/querydsl/issues/721
https://groups.google.com/g/querydsl/c/PJX9o6yxx-A