본문 바로가기
📕 Spring Framework/Spring 개념 정리

[Spring data JPA] N+1 문제 해결

by GroovyArea 2023. 2. 28.

 

이렇게 이루어진 ERD 모델이 있다. (예시)

 

조건에 맞춰 불러오고자 하는 Data는 세 테이블의 정보를 모두 필요로 한다.

이때 Fetch join을 고려해볼 수 있다.

 

Permission 입장에서 user와 document를 두번 다 fetch join 할 수 있지만, 이능 데이터 베이스 성능 상 엄청난 문제가 있다.

데이터가 많을 경우, 연관된 엔티티의 수를 제한하는 방법으로 쿼리를 나눠서 발생시키는 것도 좋은 방법일 것이다.

 

해당 엔티티들은 모두 FetchType.LAZY 로 설정되어 있다.

 

기존 쿼리 :

val documents = documentQueryService.findDocumentsByIdFetchJoinPermissions(projectId)
        
        return Response.UserPermission(
            documents.permissions.asSequence()
                .filter { it.status != STATUS.DELETED }
                .map { Response.UserPermission(it.user, it.permission) }
                .toList()
        )
  • document의 permissions 를 fetch join한 결과를 가지고 it.user를 통해 시퀀스를 돌린다.
  • 이때 permission의 개수만큼 user를 select 하는 쿼리가 계속적으로 발생한다.
  • DB Connection 낭비 및 불필요한 Query 의 발생

기존 쿼리의 API time

개선 쿼리 :

val documents = documentQueryService.findDocumentsByIdFetchJoinPermissions(projectId)
val permissions = permissionQueryService.findPermissionsWithFetchJoinUsers(project.permissions)

        return Response.UserPermission(
            documents.permissions.asSequence()
                .filter { it.status != STATUS.DELETED }
                .map { Response.UserPermission(it.user, it.permission) }
                .toList()
        )
  • user 를 불러오는 쿼리를 없애기 위해 user와 permission을 fetchjoin 하는 쿼리를 추가로 발생시켰다.
  • 이렇게 하면 user 들을 따로 select하는 쿼리가 발생하지 않는다.
  • 이런식으로 one to many 관계의 fetch join을 동시에 하는 것이 아닌 나눠서 발생시키는 것으로 쿼리 개수를 줄일 수 있다.

개선된 API 2배 이상 감소한 것을 알 수 있다.

 

고찰

백엔드의 API 성능을 개선하는 방법은 정답이 없다.

상황에 맞게 타협을 봐야 하는 부분도 당연히 고려해야한다.

 

보통 백엔드에서 성능을 가장 많이 개선시킬 수 있는 부분은 DB와의 커넥션이므로, JPA를 이용하여 SQL을 잘 발생시키면 대부분의 성능 문제는 해결할 수 있을 것으로 보인다.

실제로 확연하게 차이난 결과를 보면 그 이점을 확실히 알 수 있을 것이다.

(user data가 5개 밖에 없는데도 두배 이상 차이난 것은, 데이터가 많아질 경우, 확실하게 성능 개선이 이루어진 것을 확인할 수 있다.) 

 

이러한 방법을 통해 더 확실하게 백엔드의 성능적 고민과 해결을 잘 해나가리라 다짐해본다.

반응형