개요
이 글은 프로젝트에서 진행했던 게시글 페이징 조회시 성능 향상 과정에 대한 글이다.
문제 상황
게시글 페이징 조회 시 게시글 데이터 뿐만 아니라 게시글의 좋아요, 댓글, 북마크 수까지 함께 반환해야 한다. 따라서 게시글과 연관된 좋아요, 댓글, 북마크 엔티티까지 함께 조회하면서 N+1 문제가 발생했다.
10만 건의 데이터 중 좋아요 수가 많은 100 건을 페이징 조회하는 테스트를 진행했다. 테스트 결과는 다음과 같다.
- 6.7 TPS
- N+1 문제 발생
- 좋아요 순 조회 시 게시글 테이블을 풀스캔
해결 과정
1. 페치 조인
가장 대표적인 N+1 문제 해결 방안으로 페치 조인이 있다. 그러나 컬렉션 페치 조인은 주의할 점이 있다. 바로 페이징 처리가 offset, limit 등의 키워드를 통해 DB에서 이뤄지지 않고, 모든 조인 결과를 메모리로 불러와서 애플리케이션 단에서 페이징 처리가 이뤄진다는 것이다. 컬렉션 페치 조인은 DB에서 테이블 간 조인을 할 때 의도치 않게 데이터 수가 증가하는 문제가 발생한다. 따라서 DB에서 offset, limit 등을 통한 정확한 페이징 처리가 불가하다. 따라서 DB에서 페이징 처리가 이뤄지지 않고 모든 데이터를 메모리로 불러와, 애플리케이션 단에서 페이징 처리가 이뤄지는 것이다. 따라서 컬렉션 페치 조인은 메모리 문제를 초래할 수 있다.
2. BatchSize
BatchSize를 설정하면 게시글과 연관된 좋아요, 댓글, 북마크 엔티티를 조회할 때 설정 사이즈만큼 하나의 쿼리로 조회하여, N+1 문제를 개선할 수 있다.
그러나 다음 문제가 발생한다.
- 다수의 메서드 생성 문제
- 좋아요, 댓글, 북마크는 게시글 엔티티의 필드 값이 아니라 별도의 엔티티이다. 따라서 좋아요, 댓글, 북마크 순으로 정렬해서 페이징 조회 시, Pageable 객체를 통한 자동 페이징 처리가 불가하다. Pageable 객체는 엔티티의 필드 값을 기준으로 정렬 및 페이징을 제공하기 때문이다.
- 따라서 좋아요/댓글/북마크 순 정렬 페이징 조회를 위한 메서드를 각각 생성해야 한다.
- 다음 예제 코드는 좋아요 순 정렬 페이징 조회 메서드이다. 이와 같은 코드를 좋아요 순 정렬, 북마크 순 정렬, 댓글 순 정렬마다 각각 생성해야 한다.
- 풀스캔 문제
- 위 예제 메서드가 생성한 쿼리는 post와 likes 테이블을 조인하는 과정에서 post 테이블의 모든 레코드를 대상으로 likes 테이블과 매칭 작업을 수행하고, 매칭 결과가 높은(좋아요 수가 많은) 레코드를 선별한다.
- 즉, 좋아요 순 정렬 페이징 조회 시 post와 likes 테이블의 조인 결과를 풀스캔한다.
3. Querydsl
Querydsl을 사용하면, 정렬 조건에 따라 동적으로 조회 쿼리를 생성할 수 있다. 그러나 좋아요 순 정렬 조회를 가정했을 때, 동적으로 쿼리가 생성되더라도, post 테이블과 likes 테이블이 조인되는 것은 같고, 또 조인 결과를 풀스캔하여 좋아요 수를 기준으로 정렬 조회를 하는 것은 같다.
4. 반정규화 및 인덱스
앞선 문제들을 해결하기 위해 좋아요/댓글/북마크 수를 게시글 엔티티의 필드로 추가하는 방법이다. 그리고 추가된 필드들에 인덱스를 걸어, 해당 필드를 기준으로 정렬 조회 시 인덱스를 통한 성능 개선이 이뤄지도록 했다.
테스트 결과 다음과 같이 개선되었다.
- 6.7 TPS → 51 TPS로 개선
- N+1 문제 해결
- 좋아요/댓글/북마크 순 정렬 페이징 조회 시, 풀스캔이 일어나지 않고 인덱스를 통해 조회한다. 그리고Pageable 객체를 통한 자동 페이징 처리가 가능하므로, 정렬 조건마다 메서드를 생성할 필요가 없다.
결과적으로, 기존 방법에 비해 TPS가 약 661.2% 증가(6.7 → 51) 했다.
'프로젝트' 카테고리의 다른 글
Redis를 통한 좋아요 수 동시성 문제 해결 (0) | 2024.11.26 |
---|---|
반정규화를 통한 조회 성능 개선 (1) | 2024.11.26 |
예약 동시성 제어 과정 (0) | 2024.08.20 |
프로젝트 중 SpringSecurity 필터 체인에서 발생한 예외를 처리한 방법 (0) | 2024.01.23 |