-
Bulk 데이터 삽입 최적화 - save() , saveAll() , batchUpdate()실전 개발해보기 2023. 12. 9. 14:36
상황
토이 프로젝트로 스포츠 경기 예매 시스템을 만들고있다.
Game 클래스는 축구,야구 등 경기를 나타내는 도메인이고
Seat 클래스는 한 경기당 생성되는 수만개의 경기 좌석이다.Seat -> Game 은 N:1 단방향 (Seat에서 @ManyToOne 으로) 매핑이 되어있다.
스포츠 경기 특성상 한 경기마다 수만개의 좌석을 확보해야한다.
이 코드 짜기 직전에 RBF에서 팀원이 spring data jpa 에서 save()와 saveAll() 의 성능 비교를 보여줘서
bulk 데이터 저장에 쉽게 접근할 수 있었다.
https://voidmelody.tistory.com/189
그래도 직접 비교해보고자 둘다 실행해봤다!
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
'실전 개발해보기' 카테고리의 다른 글
redis 캐시 적용기 ( 부하테스트를 곁들인... ) (0) 2024.03.21 Service에서 Service를 참조해야만하나? (with Facade 패턴) (1) 2024.02.04 동시성 문제 해결하기(1) - DB Lock (0) 2024.01.23 clone과 fork 를 기반으로 git 이해하기 (0) 2023.08.29