카테고리 없음

[코드 리뷰] - 배운 점 (프로젝트 전반에 걸치 n+1 문제 해결)

yongyongcoding 2025. 9. 10. 13:22

"내가 맡은 부분은 프로젝트 전반에 걸쳐 고질적인 문제인 N puls 1 문제를 해결하는 것이었다.

N +1 문제는 크게 2가지로 나눌 수 있다,

첫 번째는, Lazy fetch에 의한 N+1이고.

두 번째는, 루프에 의한 반복접근 N+1문제이다."

 

[개선 사항]

1. 우선 Lazy Fetch에 대한 문제를 우선적으로 해결했다.

- 프로젝트 엔티티 내에 fetch 전략이 EAGER로 된 경우들이 있었기 때문에 LAZY 전략으로 바꿔주었다.

- 그 다음 레포지토리가 사용된 메소드를 하나씩 확인해서 fetch join이 필요한 필드들을 확인하였다.

- 필요한 필드들에 대해서는 여러 방법들이 존재했다.

   1) 아주 간단하게 @EntityGraph사용하기 - 해당 프로젝트에 내가 적용한 방법으로 확장성을 고려해 우선 EntityGraph를 적용함.

   2)  JPQL로 직접 쿼리문 작성하여 fetch join 사용하기 - 하지만 우선적으로 jpql의 단점인 컴파일 에러 파악이 어렵다는 문제의 위험성을 인지하고 이미 프로젝트가 배포된 상태이기 때문에 문제 상황을 줄이기 위해 1)번 방법을 적용함

   3) QueryDsl로 적용하기 - queryDsl을 사용한 경우에 대한 N+1문제에 대하서는 fetch join만 추가해서 해결함.

 

2 . 다음으로 루프에 의한 N+1문제를 해결하여 성능을 높였다.

- Lazy Fetch에 대한 문제를 해결하는 과정에서 과도하게 db에 접근하는 모습을 볼 수 있었다. 

- 문명 Lazy Fetch에 대한 문제가 없는데도 반복되는 sql문이 출력이 되고 있었고 이것이 루프에의한 N+1문제라는것을 알게되었다.

- 다음과 같은 방식으로 진행하였다.

   1) 루프로 단건 조회의 경우 일괄 조회인 findAll로 수정

   2) 관련된 여러 테이블에 대한 반복 조회의 경우 join을 통해 일괄 조회로 수정

   3) 추후 save 또한 saveAll을 통해 일괄 저장으로 db 트래픽을 줄 일 수 있음

 

3. 지속적인 DB접근에 대한 성능을 높이기 위해 모니터링 가능한 sql문 전용 로그 파일의 필요성.

- 사실 눈으로 코드만 봐서는 어디서 db접근에 문제가 발생하는지 확인하기는 어려움이 많다.

- 그래서 나는 로컬에서 진행할 때는 spring.jpa.show-sql 설정을 true로 해서 직접 눈으로도 확인했다.

- 지속적인 유지보수를 위해 파일을 만들면 좋겠다고 생각이 들었다.

 

4.  실제 개선 과정

- 실제 전적 업데이트 요청에 대한 성능 테스트를 진행했다.

- 현재까지 한 소환사에 대한 15개 update 시 약 7분이 소요 되었는데 -> 현재 15개 업데이트 시 5분 40초 가량이 소요되었다.

- player마다 게임 상황이 다르기 때문에 정확한 테스트는 아니었지만 대략적으로 보았을 때 현재까지 약  20%가량 성능 향상이 이루어졌음을 확인할 수 있었다.

https://www.notion.so/n-1-2693a298bc0a80f49120f17f4595489d?source=copy_link

[느낀 점]

1. 기본적으로 Lazy Fetch에 의한 N+1문제를 주의하고 싶다면 서비스 로직에 대한 return값은 DTO를 사용하자이다.
왜냐하면 복합적으로 서비스내에 다른 서비스를 사용하는 경우도 존재할텐데 이런 경우 N+1문제를 무시하게 되는 경우가 종종 발생할 수 있기 때문에 Service로직의 return은 DTO를 사용하는 것이 N+1문제 위험을 줄이는 기본인 될것 같다고 생각이 들었다.

2. 해당 repository가 사용되는 service에 대해서는 개발하면서 잘 신경써주는게 중요하다. 그와 동시에 역할 분리, 책임 분리가 잘 된 코드일 수록 N+1문제에 대해 해결도 쉽고 방지하기도 쉽다는 것을 느낄 수 있었다.

3.  N+1문제가 지연로딩에 의해서만 발생하는줄 알았는데 루프에 의해 과도한 db접근의 경우도 N+1이라고 하는 것을 알 수 있었다. 또한 이러한 과도한 db접근을 줄임으로써(개별 save 대신 saveAll 등...) 성능을 개선할 수 있다는 것도 새롭게 알게 되었다.