마지막 요구사항인 부하테스트를 artillery를 이용해 진행해 보니 게시물 조회 API에 대한 성능 문제를 발견할 수 있었다. 쿼리문을 확인해 보니 여러 개의 select 문이 찍혀있는 것을 확인할 수 있었고, 찾아보니 이 문제가 흔히 말하는 N+1 문제였다. 게시물 조회 시 게시물이 작성자(사용자), 좋아요 수와 연관 관계가 설정되어 있어 조회된 데이터 개수만큼 연관 관계 조회 쿼리가 추가되고 있었다. 따라서 불필요한 조회로 성능 저하 문제를 발생시키고 있었다. N+1 문제를 개선하고 부하테스트를 진행해 그 전과 비교해 보았다.
❓문제 상황
Hibernate: // 게시물 조회
/* <criteria> */ select
p1_0.id,
p1_0.content,
p1_0.created_at,
p1_0.modified_at,
p1_0.title,
p1_0.user_id
from
post p1_0
order by
p1_0.created_at desc
limit
?, ?
// ...
Hibernate: // 작성자 정보 조회 * 5
select
u1_0.id,
u1_0.active,
u1_0.created_at,
u1_0.description,
u1_0.email,
u1_0.modified_at,
u1_0.nickname,
u1_0.password,
u1_0.profile_image,
u1_0.role
from
user u1_0
where
u1_0.id=?
// ...
Hibernate: 좋아요 누른 작성자 조회 * 10
select
pl1_0.post_id,
pl1_0.id,
u1_0.id,
u1_0.active,
u1_0.created_at,
u1_0.description,
u1_0.email,
u1_0.modified_at,
u1_0.nickname,
u1_0.password,
u1_0.profile_image,
u1_0.role
from
post_like pl1_0
left join
user u1_0
on u1_0.id=pl1_0.user_id
where
pl1_0.post_id=?
DB에 post 10개, 작성자 5명(총 사용자는 6명), 좋아요 누른 정보 10개가 들어있었다. 게시물 조회 시 메인 게시물 정보 조회 쿼리 (1번), 작성자 정보 조회 쿼리 (5번), 좋아요 정보 조회 쿼리 (10번) 총 16번의 쿼리가 실행되었다. 모두 게시물 목록을 완전히 구성하기 위해 필요한 데이터를 가져오는 데 사용된 쿼리다.
📍해결 과정 - Artilley 설치 및 실행
우선 부하테스트를 진행하기 위해 가장 많이 사용하는 artillery를 사용해서 진행했다. 처음 테스트해 보기 때문에 제일 많이 사용하는 툴인 아틸러리를 선택했다.
- 설치 npm install -g artillery@latest
- 버전 확인 artillery --version

설치 완료 - test-config.yaml 작성
- 실행 artillery run --output report.json test-config.yaml
- 결과 파일 html 파일로 만들어 주고 확인 artillery report ./report.json
💡해결 방법
테스트를 위해 사용자 1000명, 게시물 1000개의 더미데이터를 넣고 테스트를 진행함
✔️ 1:N의 관계인 좋아요 테이블에 @BatchSize 사용
연관된 좋아요 데이터를 일정량만큼 묶어서 한 번에 가져올 수 있도록 수정


✔️ N:1의 관계인 user는 fetch join 사용
게시물을 조회하는 쿼리와 함께 관련된 사용자 정보를 한 번의 쿼리로 불러올 수 있도록 수정

📉 테스트 결과
Hibernate:
/* SELECT p FROM Post p JOIN FETCH p.user
order by p.createdAt desc */ select
p1_0.id,
p1_0.content,
p1_0.created_at,
p1_0.modified_at,
p1_0.title,
u1_0.id,
u1_0.active,
u1_0.created_at,
u1_0.description,
u1_0.email,
u1_0.modified_at,
u1_0.nickname,
u1_0.password,
u1_0.profile_image,
u1_0.role
from
post p1_0
join
user u1_0
on u1_0.id=p1_0.user_id
order by
p1_0.created_at desc
limit
?, ?
Hibernate:
/* SELECT count(p) FROM Post p JOIN p.user */ select
count(p1_0.id)
from
post p1_0
join
user u1_0
on u1_0.id=p1_0.user_id
Hibernate:
select
pl1_0.post_id,
pl1_0.id,
u1_0.id,
u1_0.active,
u1_0.created_at,
u1_0.description,
u1_0.email,
u1_0.modified_at,
u1_0.nickname,
u1_0.password,
u1_0.profile_image,
u1_0.role
from
post_like pl1_0
left join
user u1_0
on u1_0.id=pl1_0.user_id
where
pl1_0.post_id in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
쿼리문은 게시물 정보와 해당 게시물을 작성한 사용자 정보 조회 쿼리(게시물과 사용자 테이블 조인), 게시물의 총 개수 조회 쿼리, 특정 게시물들에 대한 좋아요 정보 조회 쿼리 총 3번의 쿼리가 실행되어 엄청나게 줄어든 것을 확인했다.


내가 원하는 만큼의 결과가 나오진 않았지만 두 개의 결과값을 비교하면 응답 시간이 이전보다 많이 개선된 것을 확인할 수 있다. http.response_time 그래프를 보면 중앙값과 95th 백분위수의 차이가 좁을수록 시스템이 일관되게 안정되고 빠르게 동작한다고 평가할 수 있는데, median과 p95의 차이도 많이 줄어들었다. 초기 상태에서의 차이는 7792.8, 개선 후의 차이는 385.5로 개선율은 약 95.05%이다. ( ((7792.8 - 385.5) / 7792.8) * 100 = 약 95.05% - 이 계산법이 맞나요...)


마지막으로 AOP를 이용한 시간 측정도 많이 줄어든 것을 확인했고, 대략 54.8% 개선되었다고 볼 수 있다.
'hanghae99 > 프로젝트' 카테고리의 다른 글
| [프로젝트 회고] 개인 프로젝트를 마치고 (0) | 2024.03.01 |
|---|---|
| [Spring Boot + Mustache] 템플릿 엔진 적용 시 겪은 문제와 해결 방법 (0) | 2024.02.28 |
| [테스트코드/단위테스트] 테스트코드 작성 시 겪은 문제와 해결 방법 2탄 (0) | 2024.02.27 |
| [테스트코드/통합테스트] 테스트코드 작성 시 겪은 문제와 해결 방법 1탄 (0) | 2024.02.25 |
| [Spring Boot] JWT + Security 인증 및 인가 구현 (0) | 2024.02.23 |