Bulk 데이터 삽입 최적화 - save() , saveAll() , batchUpdate()
상황
토이 프로젝트로 스포츠 경기 예매 시스템을 만들고있다.
Game 클래스는 축구,야구 등 경기를 나타내는 도메인이고
Seat 클래스는 한 경기당 생성되는 수만개의 경기 좌석이다.
Seat -> Game 은 N:1 단방향 (Seat에서 @ManyToOne 으로) 매핑이 되어있다.
스포츠 경기 특성상 한 경기마다 수만개의 좌석을 확보해야한다.
이 코드 짜기 직전에 RBF에서 팀원이 spring data jpa 에서 save()와 saveAll() 의 성능 비교를 보여줘서
bulk 데이터 저장에 쉽게 접근할 수 있었다.
https://voidmelody.tistory.com/189
JPA의 save와 saveAll의 성능차이
spring batch를 통해 데이터를 가공하고 저장하는 업무가 있었다. 당시 나는 for문을 통해 단순히 한 건마다 save를 진행해서 코드를 작성했는데 생각보다 처리 시간이 너무나 오래 걸려 이를 최적화
voidmelody.tistory.com
그래도 직접 비교해보고자 둘다 실행해봤다!
GameService
save()
//game 생성할때 좌석도 생성을 해야함.
public void createSeat(Game game) {
for (long i = 1L; i <= 100_000; i++) {
seatRepository.save(Seat.builder()
.id(i)
.seatType(SeatType.VIP)
.game(game)
.availability(true)
.build());
}
}
saveAll()
//game 생성할때 좌석도 생성을 해야함.
public void createSeat(Game game) {
List<Seat>seats = new ArrayList<>();
for (long i = 1L; i <= 100_000; i++) {
seats.add(Seat.builder()
.id(i)
.seatType(SeatType.VIP)
.game(game)
.availability(true)
.build());
}
seatRepository.saveAll(seats);
}
팀원이 말한대로 거의 두배정도의 차이를 보였다.
한 경기 테스트하는데 54초?..
saveAll()도 결국 일일히 save()를 날리기 때문에 여전히 오래걸린다.
spring data jpa repository 인터페이스를 이용하지 않고
jdbc 순수 메서드인 batchUpdate()가 있었다.
GameService
public void createSeat(Game game) {
//game 생성할때 좌석도 생성을 해야함.
List<Seat> seats = new ArrayList<>();
for (long i = 1L; i <= 100; i++) {
seats.add(Seat.builder()
.id(i)
.seatType(SeatType.VIP)
.game(game)
.availability(true)
.build());
}
seatBulkRepository.saveAll(seats);
}
SeatBulkRepository ( jpa x, 순수 클래스)
@Transactional
public void saveAll(List<Seat> seats){
String sql = "INSERT INTO seat(id,seat_type,game_id,availability) "+
"VALUES (?, ?, ?, ? )";
jdbcTemplate.batchUpdate(sql,
seats,
seats.size(),
(PreparedStatement ps, Seat seat) -> {
ps.setLong(1, seat.getId());
ps.setString(2, String.valueOf(seat.getSeatType()));
ps.setBytes(3, uuidToBytes(seat.getGame().getId()));
ps.setBoolean(4, seat.isAvailability());
});
}
batchUpdate는
하나의 레코드마다 insert 쿼리가 날라가지 않는다. 쌓아서 한번에 날라간다.
예를들어 2개의 객체를 저장할때
spring data jpa 의 save()는
insert into table_name(col1,col2) values (val1,val2);
insert into table_name(col1,col2) values (val1,val2);
두번 날라간다.
반면 jdbc의 메서드인 batchUpdate()는
insert into table_name(col1,col2) values (val1,val2),(val3,val4);
한번에 날라간다.
그덕분에 10만개의 데이터를 저장함에도 2초라는 비약적인 시간 단축을 할 수 있었다.
Reference