티스토리 뷰
개요
- 현재 내가 몸 담고 있는 프로젝트는 세금 계산과 관련되어 한 번에 수십에서 수백만의 엔티티 생성이 발생하는 비지니스 로직이 존재하며, 레코드 생성의 속도가 곧 경쟁력이 되기에 최적화에 있어 아주 중요한 요소이다. 이번 글에서는 기존에 대량의 엔티티가 단건으로 저장되던 것을 Buik Insert로 개선하는 과정을 정리하였다.
MySQL 커넥션 스트링 추가
- MySQL은 반다시
rewriteBatchedStatements=true
옵션을 커넥션 스트링에 추가해야 Bulk Insert가 활성화된다. Hikari Pool을 이용하여DataSource를 생성할 경우 아래와 같이 옵션을 추가하면 된다.
val config = HikariConfig().apply {
...
addDataSourceProperty("rewriteBatchedStatements", true)
}
val datasource = HikariDataSource(config)
FooRepository#saveAll 작성
- 아래는 실제 코드에서
JdbcTemplate#batchUpdate
를 사용하여 Foo라는 엔티티의 목록을 배치로 생성하는 Bulk Insert의 실행 예이다.
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Repository
import org.springframework.transaction.annotation.Isolation
import org.springframework.transaction.annotation.Transactional
import java.math.BigDecimal
import java.sql.Timestamp
@Repository
@Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED)
class FooRepositorySupport(
private val jdbcTemplate: JdbcTemplate
) {
@Transactional(isolation = Isolation.READ_COMMITTED)
fun saveAll(foos: List<Foo>) {
jdbcTemplate.batchUpdate(
"INSERT INTO foo (string, long, double, boolean, instant) VALUES (?, ?, ?, ?, ?)",
foos,
4096
) { ps, foo ->
foo.string?.let { ps.setString(1, it) } ?: ps.setNull(1, java.sql.Types.NULL)
foo.long?.let { ps.setLong(2, it) } ?: ps.setNull(2, java.sql.Types.NULL)
foo.double?.let { ps.setBigDecimal(3, BigDecimal.valueOf(it)) } ?: ps.setNull(3, java.sql.Types.NULL)
foo.boolean?.let { ps.setBoolean(4, it) } ?: ps.setNull(4, java.sql.Types.NULL)
foo.instant?.let { ps.setTimestamp(5, Timestamp.from(it)) } ?: ps.setNull(5, java.sql.Types.NULL)
}
}
}
- 예제 설명을 위해 Foo 엔티티가 String, Long, Double, Boolean, Instant 타입의 필드를 가진 것으로 가정했다. 전달되는 파라메터가 null일 경우 그대로 실행하면 오류가 발생하기 때문에 예외 처리 구문을 추가했다.
- JdbcTemplate#batchUpdate의
batchSize
파라메터로 하나의 배치 단위에 생성할 엔티티 개수를 지정할 수 있다. 위 예제에서는 4096을 지정했는데, 이 것만으로 운영 환경에서 22만건의 엔티티의 생성 소요시간이 10배가 단축된 것을 확인했다. - MySQL의 경우, 한 번에 실행되는 쿼리의 크기가 클 경우
max_allowed_packet
파라메터에 의해 제한이 걸려 ER_NET_PACKET_TOO_LARGE 오류가 발생한다. max_allowed_packet와 batchSize 파라메터를 적절히 튜닝해야 한다.
적용 후기
- JdbcTemplate#batchUpdate로 구현한 Bulk Insert 성능은 드라마틱하다. 앞서 언급했듯이 운영 환경에서 22만건의 엔티티의 생성 소요시간이 10배가 단축된 것을 확인했다.
- Type-Safe의 장점을 누구보다 잘 알고 추구하기에 평소 코드 레벨에서 RAW SQL의 사용은 최대한 지양하려고 노력했지만, 기존 데이터베이스 스키마의 변경 없이 속도 개선이 가능하다는 것은 상당한 이득이었다.
참고 글
댓글
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- 로드바이크
- kotlin
- 태그를 입력해 주세요.
- JavaScript
- node.js
- Tomcat
- 알뜰폰
- spring
- Kendo UI
- graylog
- Docker
- Spring Boot
- maven
- CentOS
- chrome
- 평속
- MySQL
- jstl
- jpa
- bootstrap
- 로드 바이크
- java
- DynamoDB
- 구동계
- 자전거
- Kendo UI Web Grid
- Spring MVC 3
- Eclipse
- JHipster
- jsp
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함