버전
ngrinder 3.5.8
mysql 8.0.35
쿼리 줄이기
N+1 문제
JPA를 사용하면서 Fetch 전략을 기본적으로 LAZY 전략을 사용했는데 이때 N+1 문제가 발생할 수 있다.
N+1 문제란 1번의 쿼리를 날렸을 때 의도치 않은 N번의 쿼리가 추가적으로 발생하는 문제로
LAZY 전략을 사용해서 엔티티를 가져온 후 일대다 연관관계를 가진 하위 엔티티를 조회할 경우 발생할 수 있다.
해결방법으로 fetch join을 사용할 수 있다.
하지만 페이징을 사용할 경우 데이터베이스로 날아가는 쿼리에는 limit과 offset이 없는데
이는 select 한 데이터들을 메모리에 저장하고 애플리케이션 레벨에서 정렬하기 때문인데 부하가 많이 걸린다.
다른 해결방법으로는 batch size를 사용할 수 있다.
엔티티를 가져온 후 연관관계를 가진 하위 엔티티를 조회할 때 in query를 사용해서
설정한 크기만큼 한 번에 가져올 수 있다.
Batch Insert
insert 문이 여러 개 날아갈 경우 이를 한 번의 insert 문으로 처리할 수 있다.
MySQL client에서 server로 보낼 수 있는 패킷 크기는 제한되어 있기 때문에 이에 유의해야 한다.
반복할일
N+1 문제 해결하기
반복할일 엔티티를 페이징해서 조회한 후
일대다 연관관계를 가진 반복일 엔티티를 조회하는데 이때 N+1 문제가 발생한다.
1개의 쿼리를 날려서 10개의 반복할일이 조회될 경우
반복일을 조회하는 10개의 쿼리가 추가적으로 날아간다.
select
repeattodo0_.todo_id as todo_id2_4_,
repeattodo0_.created_time as created_3_4_,
repeattodo0_.modified_time as modified4_4_,
repeattodo0_.deadline as deadline5_4_,
repeattodo0_.delete_status as delete_s6_4_,
repeattodo0_.room_id as room_id7_4_,
repeattodo0_.todo_item as todo_ite8_4_,
repeattodo0_.users_id as users_id9_4_
from
todo repeattodo0_
where
repeattodo0_.dtype='R'
and repeattodo0_.room_id=?
and repeattodo0_.delete_status=?
order by
repeattodo0_.created_time desc limit ?
select
repeatdayl0_.todo_id as todo_id3_2_0_,
repeatdayl0_.repeat_day_id as repeat_d1_2_0_,
repeatdayl0_.repeat_day_id as repeat_d1_2_1_,
repeatdayl0_.day as day2_2_1_,
repeatdayl0_.todo_id as todo_id3_2_1_
from
repeat_day repeatdayl0_
where
repeatdayl0_.todo_id=?
batch size를 적용하면 in query를 사용해서 한 번에 가져온다.
select
repeatdayl0_.todo_id as todo_id3_2_1_,
repeatdayl0_.repeat_day_id as repeat_d1_2_1_,
repeatdayl0_.repeat_day_id as repeat_d1_2_0_,
repeatdayl0_.day as day2_2_0_,
repeatdayl0_.todo_id as todo_id3_2_0_
from
repeat_day repeatdayl0_
where
repeatdayl0_.todo_id in (
?, ?, ?, ?, ?, ?, ?, ?, ?, ?
)
Batch Insert 사용하기
기존에는 cascade 설정을 통해 반복할일 엔티티를 영속화하고 저장하면서
자식 엔티티인 반복일 엔티티도 같이 영속화하고 저장되도록 했다.
이때 insert 문은 여러 개 날아간다.
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?)
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?)
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?)
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?)
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?)
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?)
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?)
다음과 같이 한 번의 insert 문으로 저장되도록 한다.
INSERT INTO repeat_day (day, todo_id) VALUES (?, ?),(?, ?),(?, ?),(?, ?),(?, ?),(?, ?),(?, ?)
테스트하기
300명의 가상유저를 설정하고 시간은 5분으로 설정했다.
스레드를 초기화하는 부분에서 유저가 로그인을 하도록 하고
반복할일 생성과 반복할일 조회하는 부분을 테스트했다.
결과적으로 성능 지표라고 볼 수 있는 TPS가 1.18배 향상된 것을 확인할 수 있다.


투표
N+1 문제 해결하기
투표 엔티티와 투표항목 엔티티를 조회한 후
투표항목마다 투표한 유저들의 정보를 조회하는데 이때 N+1 문제가 발생한다.
1개의 쿼리를 날려서 1개의 투표가 조회되고
1개의 쿼리를 날려서 3개의 투표항목이 조회될 경우
투표한 유저들의 정보를 조회하는 3개의 쿼리가 추가적으로 날아간다.
select
vote0_.vote_id as vote_id1_8_,
vote0_.created_time as created_2_8_,
vote0_.modified_time as modified3_8_,
vote0_.delete_status as delete_s4_8_,
vote0_.vote_status as vote_sta5_8_,
vote0_.room_id as room_id6_8_,
vote0_.title as title7_8_,
vote0_.users_id as users_id8_8_
from
vote vote0_
where
vote0_.vote_id=?
and vote0_.delete_status=?
select
voteitem0_.vote_item_id as vote_ite1_9_,
voteitem0_.created_time as created_2_9_,
voteitem0_.modified_time as modified3_9_,
voteitem0_.choice as choice4_9_,
voteitem0_.vote_id as vote_id5_9_
from
vote_item voteitem0_
where
voteitem0_.vote_id=?
select
user1_.profile_img as col_0_0_,
user1_.nickname as col_1_0_,
user1_.users_id as col_2_0_
from
user_vote_item uservoteit0_
inner join
users user1_
on (
uservoteit0_.users_id=user1_.users_id
)
where
uservoteit0_.vote_item_id=?
and uservoteit0_.delete_status=?
in query를 써서 한 번에 가져오도록 쿼리를 수정한다.
select
user1_.profile_img as col_0_0_,
user1_.nickname as col_1_0_,
user1_.users_id as col_2_0_,
voteitem2_.choice as col_3_0_,
uservoteit0_.vote_item_id as col_4_0_
from
user_vote_item uservoteit0_
inner join
users user1_
on (
uservoteit0_.users_id=user1_.users_id
) cross
join
vote_item voteitem2_
where
uservoteit0_.vote_item_id=voteitem2_.vote_item_id
and uservoteit0_.delete_status=?
and (
uservoteit0_.vote_item_id in (
? , ? , ?
)
)
Batch Insert 사용하기
기존에는 cascade 설정을 통해 투표 엔티티를 영속화하고 저장하면서
자식 엔티티인 투표 항목 엔티티도 같이 영속화하고 저장되도록 했다.
이때도 insert 문은 여러 개 날아간다.
INSERT INTO vote_item (choice, vote_id) VALUES (?, ?)
INSERT INTO vote_item (choice, vote_id) VALUES (?, ?)
INSERT INTO vote_item (choice, vote_id) VALUES (?, ?)
다음과 같이 한 번의 insert 문으로 저장되도록 한다.
INSERT INTO vote_item (choice, vote_id) VALUES (?, ?),(?, ?),(?, ?)
테스트하기
300명의 가상유저를 설정하고 시간은 5분으로 설정했다.
스레드를 초기화하는 부분에서 유저가 로그인을 하도록 하고
투표 생성과 투표 조회하는 부분을 테스트했다.
TPS가 1.1배 향상된 것을 확인할 수 있다.


'프로젝트 > 집안일 관리 시스템' 카테고리의 다른 글
| [보안 강화] Refresh Token을 사용해보자 (2) | 2025.08.10 |
|---|---|
| [기능 구현] 메모 기능에서 WebSocket을 사용해보자 (0) | 2025.08.05 |
| [성능 개선] Batch 작업을 최적화해보자 (1) | 2024.12.04 |
| [성능 개선] 유니온 쿼리를 개선해보자 (0) | 2024.01.08 |
| [성능 개선] 인덱스를 사용해보자 (0) | 2024.01.06 |