상황
현재 스케줄을 등록할 때 기존 스케줄을 deleteAll한 뒤, 새로운 스케줄을 saveAll하는 방식을 취하고 있다.
기존에는 하루씩 일정을 등록할 수 있었으나, 사용자의 피드백을 반영하는 과정에서 다중선택 기능이 추가되었다.
따라서 여러 날의 일정을 한번에 등록할 수 있게 되었다.
문제점
여러 스케줄을 등록할 때 스케줄 하나하나마다 save 쿼리가 나가 DB에 부하를 주고 있는 상황이다.
코드
private void saveAllByCoachAndDate(Long coachId, ScheduleUpdateRequest request) {
Coach coach = findCoach(coachId);
List<Schedule> schedules = toSchedules(request, coach);
scheduleRepository.saveAll(schedules); // 이 부분에서 성능 문제가 발생하고 있었다!
}
sql 쿼리문
해결방안
JdbcTemplate 사용
JPA로는 saveAll 호출 시 쿼리가 하나하나 발생하는 문제를 해결할 수 없었다.
왜냐하면 JPA에서는 엔티티를 저장하면 그 엔티티를 영속성컨텍스트에 저장하기 위해 식별자가 필요하기 때문이다.
그래서 saveAll을 사용하더라도 Batch Insert가 아닌, 엔티티 하나하나 쿼리문을 날려줄 수 밖에 없다.
이 문제를 해결하기 위해서는 JPA가 아닌 JdbcTemplate을 사용해야만 한다.
DAO
@RequiredArgsConstructor
@Repository
public class ScheduleDao {
private static final int BATCH_SIZE = 1000;
private final JdbcTemplate jdbcTemplate;
public void saveAll(List<Schedule> schedules) {
int batchCount = 0;
List<Schedule> subItems = new ArrayList<>();
for (int i = 0; i < schedules.size(); i++) {
subItems.add(schedules.get(i));
if ((i + 1) % BATCH_SIZE == 0) {
batchCount = batchInsert(batchCount, subItems);
}
}
if (!subItems.isEmpty()) {
batchCount = batchInsert(batchCount, subItems);
}
}
private int batchInsert(int batchCount, List<Schedule> subItems) {
jdbcTemplate.batchUpdate("INSERT INTO schedule (coach_id, local_date_time, is_possible) VALUES (?, ?, ?)",
new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
ps.setLong(1, subItems.get(i).getCoach().getId());
ps.setTimestamp(2, Timestamp.valueOf(subItems.get(i).getLocalDateTime()));
ps.setBoolean(3, subItems.get(i).getIsPossible());
}
@Override
public int getBatchSize() {
return subItems.size();
}
});
subItems.clear();
batchCount++;
return batchCount;
}
}
추가로 생각해볼 점
JPA와 JdbcTemplate을 함께 사용할 때 주의할 점
JPA의 구조적인 한계 때문에 saveAll() 로직에서 JdbcTemplate을 사용할 수 밖에 없었다.
자연스레 JPA와 JdbcTemplate을 함께 사용하게 되었다. 하나의 WAS에서 JPA와 JdbcTemplate을 함께 사용할 때 주의해야 할 점은 무엇일까?
만약 JdbcTemplate을 조회 로직에서도 사용한다면 문제가 발생할 수 있다.
왜냐하면 JdbcTemplate은 영속성 컨텍스트를 거치지 않고 바로 DB를 조회하기 때문에 데이터 정합성에 문제가 발생할 수 있기 떄문이다. 그래서 JdbcTemplate을 조회시 사용한다면 JdbcTemplate을 사용하기 전에 영속성 컨텍스트를 flush해서 데이터의 정합성을 보장해야 한다.
JdbcTemplate 사용 전 flush를 AOP로 처리하는 방법도 추후에 고민해봐야겠다.
출처
'우아한테크코스' 카테고리의 다른 글
JPA 성능 개선기 3. 페치조인 (0) | 2022.10.19 |
---|---|
JPA 성능 개선기 2. 1+N 문제 우아하게 해결하기 (0) | 2022.10.03 |
JPQL을 사용할 때는 join 타입을 꼭 명시하자 (1) | 2022.09.29 |
JPA 성능 개선기 1. 쿼리 개수 및 시간 로깅 (0) | 2022.09.28 |
Mysql 쿼리최적화 (2) | 2022.09.25 |