개요
경매 글 목록을 불러오는 api의 구조를 개선하여 목표하는 성능까지 올려보는 과정을 기록하고자 해당 시리즈를 작성하기로 하였습니다.
본론에 앞서서 해당 프로젝트의 더미 데이터로 유저 약 100만, 기프티콘 개수 약 2천만, 경매 글 개수 약 100만 개를 생성해놓은 상태입니다.
문제 인식
부하 테스트를 하기 전, postman으로 먼저 간단하게 응답 시간이 얼마나 걸리는지 테스트 해봤습니다.
응답 시간이 매우 느립니다.. 네 이건 뭐 부하테스트를 할 필요도 없이 먼저 해당 API의 문제를 살펴보는 것이 좋을 것 같다는 판단을 했습니다.
전체적인 로직을 파악해본 결과 문제는 아래 부분 정도로 정의했습니다.
1. 페이지네이션 오프셋 성능 저하 문제
2. N + 1 문제
3. queryDSL 에서 cross join 발생
페이지네이션 문제
보통 페이지네이션을 구현할 때 백엔드단에서 구현하는 대표적인 방법은 2가지가 있습니다.
1. 오프셋 기반
2. 커서 기반
각 방법의 구현 방법이나 여러 부분들은 다른 분들이 정리도 해주셨고, 주제에서 조금 벗어나기 때문에 따로 다루진 않겠지만 대표적인 장단점을 생각했습니다.
오프셋 기반 페이지네이션의 장점은 다음과 같습니다.
1. 페이지 정보를 제공
2. 페이지 정보를 통해 원하는 페이지를 한번에 조회할 수 있다는 것
3. 구현이 간단
반면에, 단점으로는 다음과 같습니다.
1. 무한 스크롤을 구현 X
2. 데이터가 쌓여서 페이지가 많아질 수록 성능이 저하
3. 데이터 추가 시 중복 문제
커서 기반 페이지네이션의 장점은 다음과 같습니다.
1. 데이터가 많아져도 성능 저하 X
2. count 쿼리 같은 추가적 비용 발생 X
반면 단점은 다음과 같습니다.
1. 클라이언트가 데이터를 가지고 있어야 한다는 부담
2. Not Unique 데이터가 기준이 될 시 구현 복잡성 증가
어느 방법이 무조건 좋다기 보다는, 상황에 맞추어서 선택을 하는 것이 좋다고 생각하는데요. 저의 경우에는 다음과 같은 측면들을 고려했습니다.
클라이언트 환경
현재 저의 프로젝트의 클라이언트 환경은 안드로이드로 제공되고 있습니다.
모바일 앱 환경에서 서비스가 제공된다는 점, UX 측면에서 오프셋 기반 페이지 제공 보다는 무한 스크롤 방식으로 구현하는 것이 더 낫다는 점이 커서 기반 페이지네이션의 단점과의 trade-off를 따져봤을 때 더 낫다고 판단되어 커서 기반 페이지네이션으로 구조를 변경하자는 선택을 하게 되었습니다.
성능
아무래도 데이터가 많아질 수록 성능 저하가 발생한다는 점이 부담이 됐습니다. 오프셋 기반의 방법의 경우 count 쿼리도 추가적으로 발생되기 때문에 데이터의 생성이 빈번해져도 성능 저하가 발생하지 않아야 한다는 부분을 고려해야 했습니다.
CROSS JOIN 발생
현재 오프셋 기반 페이지네이션 구현 방법으로 queryDSL을 사용하고 있습니다.
하지만 아래 실행되는 쿼리를 살펴보면 CROSS JOIN이 발생하는 것을 볼 수 있습니다.
사실 원인은 queryDSL 자체가 문제가 아니라 현재 프로젝트에서 사용하고 있는 JPA의 구현체인 Hibernate의 암묵적인 규칙 때문입니다.
Hibernate의 경우에 명시적인 JOIN을 사용하지 않을 경우 CROSS JOIN을 사용합니다.
그렇기 때문에 JPQL을 사용할 때도 발생하게 됩니다.
해당 부분은 명시적으로 필요한 JOIN 방법을 사용하면 되기 때문에 비교적 해결 방법은 쉬우나, 원인을 모른다면 은근히 헤맬 수 있는 부분이라고 생각했습니다.
N + 1 문제
항상 JPA를 사용하면서 도사리고 있는 문제입니다..
불러온 경매글 목록에 대해서 1:1로 매핑된 기프티콘 정보를 N 번의 쿼리로 또 다시 불러오고 있습니다.
기술 스택을 선택하고 적용해서 잘 사용하려면, 사용했을 때 장점도 중요하지만 잘못 사용했을 때 발생하는 사이드 이펙트나 단점들을 명확히 알고 사용하는 것이 더 중요하다는 것을 공부하면서 점점 느끼게 되는 것 같습니다.
다음 포스팅에는 해당 문제들을 해결하여 부하테스트를 진행하고 어느정도 성능 개선이 진행됐는지 확인하도록 하겠습니다.
Reference
[JPA] N+1 문제가 발생하는 여러 상황과 해결방법 — Shin._.Mallang (tistory.com)
[JPA] N+1 문제가 발생하는 여러 상황과 해결방법
🧐 N + 1 문제 N + 1 문제는 연관관계가 설정된 엔티티 사이에서 한 엔티티를 조회하였을 때, 조회된 엔티티의 개수(N 개)만큼 연관된 엔티티를 조회하기 위해 추가적인 쿼리가 발생하는 문제를 의
ttl-blog.tistory.com
Querydsl (JPA) 에서 Cross Join 발생할 경우 (tistory.com)
Querydsl (JPA) 에서 Cross Join 발생할 경우
JPA 기반의 환경에서 Querydsl를 사용하다보면 @OneToOne 관계에서 Join 쿼리 작성시 주의하지 않으면 Cross Join이 발생할 수 있습니다. CrossJoin 이란 집합에서 나올 수 있는 모든 경우를 이야기 합니다.
jojoldu.tistory.com
Cursor based Pagination(커서 기반 페이지네이션)이란? - Querydsl로 무한스크롤 구현하기 (velog.io)
'Dev > 트러블 슈팅' 카테고리의 다른 글
[conseller] 경매 목록 조회 API 개선 - Cursor 기반 페이지네이션으로 전환 (2) | 2024.09.25 |
---|