Artillery를 이용하여 간단하게 테스트를 해 본 지난 글에 이어 프로젝트에 부하 테스트를 적용시켜 보는 이번 글
게시글 기능이 유저, 멀티미디어, 게시글 좋아요까지 여러 엔티티와 연관관계로 묶여 있는 데다가 피드 서비스의 핵심 기능이기 때문에 게시글 전체 목록을 받아오는 API를 선택해서 성능을 제대로 측정해 보기로 했다.
성능 저하를 막기 위해 sql 로깅 기능을 끄고 유저 1000명, 게시글 1000 정도를 더미데이터로 넣어 테스트하였다.
더미데이터 생성 사이트 - https://www.mockaroo.com/
1차 테스트
로컬에서 진행하기 위해 URL을 로컬 서버로 지정하고 테스트 구성을 아래와 같이 하였다.
- Warm up: 30초 동안 10개 요청
- Ramp up load: 20초 동안 20개의 요청에서 시작하여 200개의 요청으로 늘림
- Sustained load: 지속적으로 고부하에 견딜 수 있는지 테스트하기 위해 20초 동안 200개의 요청이 일정한 속도로 유지
- End of load: 30초 동안 200개의 요청에서 시작하여 20개의 요청으로 줄임
약 1만 개의 call을 목표로 진행하였으며 결과는 아래처럼 나왔다.
총 9800개의 요청으로 실패 없이 모두 응답하였다.
하지만 단순히 얼마나 많이 요청에 응답을 했느냐만 보면 안 된다. 이건 컴퓨터 사양이나 현재 실행 중인 프로그램이 얼마냐 되느냐에 따라 달라질 수 있는 거라 결과 아래쪽에 보이는 http.response_time에서 median - p95 - p99 간의 차이를 유의미하게 보아야 한다.
http.response.time에서 응답 시간에 대한 통계를 한눈에 볼 수 있는데, median(중앙값)과 p95(95 백분위) 값의 차이가 상당히 나는 것 같다.
median 36.2ms
p95 2,186ms
p99 4,492ms
두 값의 차이가 많이 나지 않는 경우는 데이터의 대부분이 중앙값 주변에 집중되어 있고 데이터가 균일하게 분포되어 있다는 것을 의미하며, 반대로 차이가 많이 날수록 데이터가 비대칭적으로 분포되어 있다고 볼 수 있다.
API 콜에 부하를 줘서 점점 더 높은 부하를 주어도 응답 속도의 중간값과 95 백분위 값의 격차가 작아야 높은 성능을 낸다고 볼 수 있다.
약 1만 건으로 1차 부하 테스트를 진행했는데 실제로는 큰 수치는 아니라고 생각한다. 10만 건 정도를 기준으로 보통 테스트를 한다고 하는데, 처음 진행해 보는 테스트이기도 하고 이미 결과에서 중간값과 p95 사이의 차이가 너무 커서 이번 테스트에서는 어떻게 하면 이 두 값의 차이를 줄일 수 있을지에 초점을 두고 코드 리팩토링을 해보고자 했다.
성능 저하 문제점 찾기
게시물 목록을 받아오기 위해서는 게시물, 유저, 멀티미디어, 게시물 좋아요 총 4가지의 테이블에서 데이터를 조회할 것이다.
Post의 엔티티 필드를 살펴보면
게시글은 사용자와 N:1 관계를 댓글, 좋아요, 멀티미디어와는 1:N 관계를 맺고 있다.
게시글 목록 조회할 때 별도의 쿼리문을 각각 작성해야 하나 고민하다가 ResponseDto에 바로 연관관계 필드를 이용해서 리턴값에다가 넣어주고는 쿼리문 따로 작성 안 해도 되는구나 하고 좋아했다.
더미데이터 넣기 전 기존의 DB로 게시글 목록 조회를 해보니 게시물 13개 유저 6명이었는데
sql 로그를 찍어보니 페이징 처리 이후 목록 조회 시 첫 페이지 10개의 게시물을 받아오기 위해
총 28번의 조회가 이루어지고 있었다.
→ 게시글 목록 조회 1, 유저 조회 6, count 1, 멀티미디어 10, 게시글 좋아요 10
사실 ResponseDto에 바로 저렇게 필드를 추가해 주면서도 JPA가 자동으로 인식해서 조회해 주는구나~ 그럼 최적의 방법으로 해주겠지? 하고 그냥 넘어갔는데 로그에 찍히는 쿼리문을 들여다보니 이게 성능저하의 주범인 것 같았다.
찾아보니 이게 바로 N+1 문제였구나.
N+1 문제란 데이터를 로드할 때 발생하는 성능 문제로 한 번의 쿼리로 가져와야 할 데이터를 여러 번의 추가 쿼리를 요청함으로써 성능이 저하되는 문제를 말한다.
보통 아래 방법들을 이용해서 해결한다고 한다.
- fetch join 사용
- @BatchSize 사용하여 일대다 관계를 배치로 변경
- @EntityGraph 사용하여 엔티티 그래프를 로드할 때 필요한 연관 엔티티를 함께 로드
- 세컨드 레벨 캐시 사용하여 두 번째부터는 같은 데이터를 캐시에서 가져옴
하나씩 적용해 보며 비교해 보고 다음 글에 결과를 작성해 봐야겠다.
<다음글>
2024.02.28 - [+++/프로젝트] - [성능 테스트][트러블 슈팅] Artillery를 이용한 부하 테스트 - 3 (성능 개선)
'프로젝트 > 뉴스피드' 카테고리의 다른 글
[회고] 뉴스피드 개인 프로젝트 마무리 (0) | 2024.03.05 |
---|---|
[성능 테스트][트러블 슈팅] Artillery를 이용한 부하 테스트 - 3 (성능 개선) (0) | 2024.02.28 |
[성능 테스트] Artillery를 이용한 부하 테스트 -1 (테스트 설정, 결과 보기) (0) | 2024.02.27 |
[단위 테스트/Mockito] 게시글 서비스레이어 단위 테스트 - 2 (with JUnit 버전 문제) (0) | 2024.02.26 |
[단위 테스트/Mockito] 게시글 서비스 레이어 단위 테스트 - 1 (with ReflectionTestUtils) (0) | 2024.02.26 |