최근 진행했던 프로젝트에서 데이터베이스 성능 문제를 해결하기 위한 여정을 공유해보고자 합니다. 프로젝트 초반에는 마이바티스(MyBatis)를 이용하여 다수의 테이블을 조인하며 데이터를 조회했지만, 성능 문제가 점점 심각해졌습니다. 프로젝트 진행 당시에는 팀원 모두가 JPA를 갑자기 학습할 수도 없으니 추후에 나 혼자 해당문제를 해결 해보자고 생각했습니다.
그래서 이후에, 이 문제를 개선하고자 JPA(Java Persistence API)를 도입했지만, 초반에는 n + 1 문제로 인해 큰 성능 개선을 이루지 못했습니다. 이 글에서는 문제 해결 과정과 그 결과를 상세히 다루어 보겠습니다.
문제 상황: 성능 저하
프로젝트 초기에는 MyBatis를 사용하여 복잡한 쿼리를 작성했습니다. 하지만 여러 테이블을 조인하는 쿼리가 많아지면서 성능이 급격히 저하되었고, 평균 응답 시간이 300ms에 달했습니다. 이는 사용자 경험에 부정적인 영향을 미쳤고, 성능 개선이 절실했습니다.
JPA 도입: 초기 문제
DB 성능 개선을 위해 ORM(Object-Relational Mapping)인 JPA를 도입했습니다. JPA를 사용하면 자동으로 SQL을 생성하고, 객체지향적인 방식으로 데이터베이스를 다룰 수 있어 성능이 향상될 것이라고 기대했습니다. 그러나, 실제로 JPA를 도입한 후 n + 1 문제에 직면하게 되었습니다.
n + 1 문제란, 하나의 쿼리를 실행할 때 관련된 n개의 쿼리가 추가로 실행되는 현상을 말합니다. 예를 들어, 하나의 부모 엔티티를 조회할 때, 관련된 자식 엔티티들을 각각 n번 조회하게 되는 문제입니다. 이로 인해 응답 시간이 200ms로 다소 개선되었지만, 기대에 못 미쳤습니다.
패치 조인: 첫 번째 개선
n + 1 문제를 해결하기 위해 패치 조인(Fetch Join)을 사용했습니다. 패치 조인은 하나의 쿼리로 여러 연관된 엔티티를 한 번에 조회할 수 있도록 해줍니다. 패치 조인을 통해 5개의 테이블을 조인하여 조회하는 쿼리를 작성한 결과, 응답 시간이 100ms로 크게 개선되었습니다.
// 예시: 패치 조인 쿼리
@Query("SELECT p FROM Parent p JOIN FETCH p.childList WHERE p.id = :id")
List<Parent> findByIdWithChildren(@Param("id") Long id);
최종 해결: 쿼리 분리
패치 조인을 통해 성능이 상당히 개선되었지만, 여전히 부족하다고 느꼈습니다. 따라서, 한 가지 작업을 위해 모든 테이블을 한 번에 조회할 필요가 없는 경우를 고려하여 쿼리를 두 개로 나누기로 했습니다. 2개의 테이블과 3개의 테이블을 각각 패치 조인하여 조회하는 방식으로 변경한 결과, 응답 시간이 30ms로 크게 줄어들었습니다.
// 예시: 첫 번째 패치 조인 쿼리
@Query("SELECT p FROM Parent p JOIN FETCH p.firstChild WHERE p.id = :id")
List<Parent> findByIdWithFirstChild(@Param("id") Long id);
// 예시: 두 번째 패치 조인 쿼리
@Query("SELECT p FROM Parent p JOIN FETCH p.secondChild JOIN FETCH p.thirdChild WHERE p.id = :id")
List<Parent> findByIdWithSecondAndThirdChild(@Param("id") Long id);
결론
이번 프로젝트를 통해 데이터베이스 성능 문제를 해결하는 과정에서 많은 것을 배울 수 있었습니다. 마이바티스에서 JPA로 전환하면서 n + 1 문제를 겪었지만, 패치 조인과 쿼리 분리를 통해 최종적으로 성능을 크게 개선할 수 있었습니다. 초기 300ms에서 중간에 200ms, 그리고 최종적으로 30ms까지 성능을 향상시킨 경험은 앞으로도 데이터베이스 성능 최적화에 큰 도움이 될 것입니다.
이러한 문제 해결 과정을 공유함으로써, 비슷한 성능 문제를 겪고 있는 다른 개발자들에게 도움이 되길 바랍니다. 성능 최적화는 한 번에 이루어지는 것이 아니라, 여러 단계의 개선 과정을 통해 이루어진다는 점을 기억하며, 지속적인 개선 노력을 이어나가야겠습니다.
'Framework&Tools > Spring' 카테고리의 다른 글
Filter, Interceptor, AOP - 차이점과 적절한 사용 상황 (0) | 2024.04.17 |
---|---|
Spring에서 MyBatis 사용하기: 세팅부터 기본 문법까지 (0) | 2024.04.13 |