프로젝트 진행중 이런 경고가 뜬다.
메모리를 많이 사용한다는 것인가?
아무튼 경고이기 때문에 좋지는 않은 상황이다.
나는 알 수 없는 문제가 생겼을때 한국 블로그를 제외하고는 StackOverFlow 나 Baeldung을 가장 많이 활용한다.
오늘은 StackOverFlow에서 원하던 정보를 찾았다.
firstResult/maxResults specified with collection fetch; applying in memory!
라는 경고가 떴다.
동작에 문제는 없지만 메모리 낭비를 한다는 내용인 것 같다.
경고를 보아 아마 fetch join 시 offset limit 을 걸어둔 것과 연관이 있는 것 같다.
찾아보니 위 경고가 의미하는 것은
fetch join 과 pagination 을 같이 할 시
"모든 데이터"를 전부 가져와 메모리에서 걸러낸다는 것이다.
이것은 당장 해결해야한다.
일단 문제되는 코드를 보자.
@Override
public List<Person> findAll(Page page, OrderSpecifier sort, BooleanExpression keyword) {
return joinQuery()
.where(keyword)
.orderBy(sort)
.offset(page.getOffset())
.limit(page.getAmount()).fetch();
}
private JPAQuery<Person> joinQuery(){
QA a = QA.a;
return queryFactory.selectFrom(qA)
.leftJoin(a.b, QB.b)
.fetchJoin()
.leftJoin(a.c, QC.c)
.fetchJoin()
.leftJoin(a.d, QD.d)
.fetchJoin();
}
A B C D 중에 A 와 C 는 1 : N 관계이다.
하나의 A 가 여러개의 C를 가질 수 있기 때문에
A 가 중복되어 반환될 수 있다.
A1 C1
A1 C2
A1 C3
이 상태에서는 페이지네이션으로 원하는 만큼의 A를 가져올 수 없다.
그래서 Hibernate 는 데이터를 모두 가져온 후
JVM 메모리상에서 필터링을 수행한다.
콘솔에 찍힌 쿼리를 봣더니
실제로 페이지네이션에 대한 부분이 빠져있었다.
위에서 말한대로 모든 데이터를 가져와서 JVM 메모리에서
페이지네이션을 한다는 것이다.
지금은 더미데이터밖에 없어서 괜찮을지 몰라도
만에하나 내 서비스를 많은 사람들이 이용한다면
엄청나게 느려질 것이다.
스택오버플로우에서 제시한 해결 방안은
쿼리를 두개로 나누자는 것이었다.
먼저 1 에 해당하는 엔티티의 주식별자들을 페이지네이션으로 가져온다
List<Long> ids = queryFactory.selectFrom(QA.a)
.offset(page.getOffset())
.limit(page.getLimit())
.fetch();
그후 IN절을 사용해 조인 쿼리를 만든다.
List<A> aList = queryFactory.selectFrom(qA)
.leftJoin(a.b, QB.b)
.fetchJoin()
.leftJoin(a.c, QC.c)
.fetchJoin()
.leftJoin(a.d, QD.d)
.fetchJoin()
.where(a.id.in(ids)) // 위에서 추출한 id들
.fetch();
쿼리를 날리는 수는 하나 많아졌지만 필요한 만큼의 데이터만 가져올 수 있게 됐다.
fetch join 과 pagination 을 같이 사용하면 자바상에서 데이터에 대한 페이지네이션을 처리하기 때문에 다른 방법을 찾아보자.