MySQL 배치 INSERT가 느린 이유: rewriteBatchedStatements
MySQL 배치 INSERT가 느린 이유: rewriteBatchedStatements
→ rewriteBatchedStatements는 MySQL JDBC 드라이버 옵션으로, 여러 개의 INSERT 문을 하나의 멀티 로우 INSERT 문으로 합쳐서 보내주는 기능이다.
2026-02-11
발단
알림센터에 알림을 벌크로 저장하는 기능이 있다. 한 번에 수천~수만 건을 INSERT 한다.
그런데 속도가 기대보다 많이 느렸다. JPA의 saveAll()로 배치 INSERT를 하고 있었고, Hibernate(JPA 표준을 구현한 가장 대표적인 ORM 프레임워크) 배치 설정도 해놨는데.
spring:
jpa:
properties:
hibernate:
jdbc.batch_size: 5000
JPA 배치가 뭔데?
saveAll()에 5,000건을 넘기면, Hibernate는 5,000개의 INSERT를 모아서 한 번에 보낸다... 라고 생각하기 쉽다.
실제로는 아니다.
Hibernate 배치 (batch_size: 5000)
→ JDBC 드라이버에 5,000개의 addBatch() 호출
→ executeBatch() 실행
여기까지는 맞다. 그런데 MySQL JDBC 드라이버(자바 애플리케이션이 MySQL 데이터베이스와 통신할 수 있게 해주는 라이브러리)가 이걸 네트워크로 보낼 때:
기본 동작 (rewriteBatchedStatements=false):
INSERT INTO t VALUES (1, 'a');
INSERT INTO t VALUES (2, 'b');
INSERT INTO t VALUES (3, 'c');
... 5,000번 반복
→ 5,000개의 개별 INSERT가 네트워크를 탄다!
Hibernate가 아무리 배치를 만들어도, MySQL 드라이버가 개별 INSERT로 풀어서 보내버리는 것이다.
rewriteBatchedStatements=true
JDBC URL에 이 옵션을 추가하면:
rewriteBatchedStatements=true:
INSERT INTO t VALUES (1, 'a'), (2, 'b'), (3, 'c'), ...;
→ 하나의 멀티 로우 INSERT로 합쳐서 전송!
# Before
url: jdbc:mysql://host:3306/mydb
# After
url: jdbc:mysql://host:3306/mydb?rewriteBatchedStatements=true
이것만으로 벌크 INSERT 속도가 수십 배 빨라질 수 있다.
왜 기본값이 false인데?
MySQL 공식 문서에 따르면:
rewriteBatchedStatements=true는 멀티 로우 INSERT로 변환할 때 SQL 구문이 변경된다- ON DUPLICATE KEY UPDATE 같은 구문에서 VALUES() 함수의 동작이 달라질 수 있다
- 호환성 이슈로 기본 꺼짐
대부분의 일반적인 INSERT에서는 켜도 문제가 없다.
같은 날 수정한 Spring Boot 초기화 이슈들
배치 성능을 수정하면서 Spring Boot 경고도 같이 정리했다.
@PostConstruct vs @EventListener(ApplicationReadyEvent)
→ 빈(Bean)은 Spring이 관리하는 객체를 말한다. Spring은 애플리케이션 시작 시 필요한 객체들을 자동으로 생성하고 관리하는데, 이 객체들이 빈이다.
// Before — @PostConstruct
@PostConstruct
public void init() {
// Kafka가 아직 초기화 안 됐을 수 있음!
checkKafkaTopics();
}
// After — ApplicationReadyEvent
@EventListener(ApplicationReadyEvent.class)
public void onReady() {
// 모든 빈 초기화 완료 후 실행
checkKafkaTopics();
}
@PostConstruct는 해당 빈이 생성된 직후 실행된다. 다른 빈이 아직 초기화가 안 됐을 수 있다.
ApplicationReadyEvent는 모든 빈 초기화가 끝난 후 발생한다.
@Configuration + @Bean = static이 안전
→ @Configuration은 이 클래스가 Spring 설정 파일 역할을 한다는 표시이고, @Bean은 이 메서드가 반환하는 객체를 Spring이 관리하라는 표시다.
@Configuration
public class MyConfig {
// Before — 인스턴스 메서드
@Bean
public Validator validator() { ... }
// After — static 메서드 (Spring Boot 3.x 권장)
@Bean
static Validator validator() { ... }
}
@Bean 메서드가 non-static이면 설정 클래스의 전체 라이프사이클(객체가 생성되고 사용되고 소멸되는 전체 과정)에 의존하게 된다. static으로 만들면 설정 클래스 인스턴스 없이도 빈을 생성할 수 있어서 초기화 순서 문제를 피할 수 있다.
배운 것
- MySQL + JPA 배치를 쓴다면
rewriteBatchedStatements=true는 거의 필수다. 안 켜면 배치가 의미 없다. - Hibernate
batch_size만으로는 충분하지 않다. 드라이버 레벨에서도 배치를 지원해야 한다. @PostConstruct대신@EventListener(ApplicationReadyEvent.class)가 안전하다. 다른 빈에 의존하는 초기화 로직이라면 특히.- Spring Boot 3.x에서
@Bean메서드는 static으로 만들 수 있다면 만들자.