반응형
이번 포스팅에서는 제가 실제 프로젝트에서 겪었던 대량 데이터 처리 성능 이슈와 이를 해결한 경험을 적어보고자 합니다.
JPA의 saveAll()
을 사용하던 로직을 JdbcTemplate의 Bulk Update로 전환하면서 얻은 성능 개선 경험을 기록해보고자 합니다.
겪었던 성능 이슈
프로젝트를 진행하면서 다음과 같은 성능 문제에 직면했습니다:
- JPA의
saveAll()
을 사용한 대량 데이터 업데이트 시 심각한 성능 저하 발생 - 약 8,000건의 데이터 처리에 약 18분이라는 긴 시간이 소요
- 각 엔티티마다 개별 UPDATE 쿼리가 실행되어 데이터베이스 부하 증가
실제 처리해야 할 데이터는 10,000건 미만이었지만, 단순 데이터 적재 외에도 여러 부가적인 처리 작업이 필요했기 때문에 꽤나 긴 시간이 소요되었습니다.
이러한 문제를 해결하기 위해 JdbcTemplate의 Bulk Update를 도입하기로 결정했습니다.
성능 측정 결과
개선 전 (JPA saveAll 사용)
- 실행 쿼리 수: 데이터 건수만큼 실행 (1000건이면 1000번)
- 평균 응답 시간: 약 18분
- 문제점:
- 각 레코드마다 개별 UPDATE 쿼리 실행
- 데이터베이스 통신 비용 증가
// AS-IS memberRepository.saveAll(memberList)
개선 후 (Bulk Update 사용)
- 실행 쿼리 수: batch size 단위로 실행 (예: 1000건을 batch size 100으로 처리 시 10번)
- 평균 응답 시간: 약 4분
- 개선율: 약 78% 성능 향상
- 개선 포인트:
- 데이터베이스 통신 횟수 대폭 감소
- 효율적인 리소스 활용
개선 방법 상세 설명
실제 코드가 아닌 예시 코드입니당
1. JdbcTemplate의 batchUpdate 설정
MySQL을 사용하는 경우 반드시 다음 설정을 추가해야 합니다:
jdbc:mysql://[DB URL]?rewriteBatchedStatements=true
2. 데이터 청크 단위 처리
메모리 효율성을 고려하여 데이터를 청크 단위로 분할하여 처리합니다:
val batchSize = 1000
val chunkedMemberList = allMembers.chunked(batchSize)
chunkedMemberList.forEachIndexed { index, chunk ->
bulkRepository.updateMembers(chunk)
log.info {
"[member] (${index + 1}/${chunkedMemberList.size}) | Bulk Update 완료 :: size=${chunk.size}"
}
}
- 적절한 배치 사이즈를 설정하여 메모리 사용량을 관리해야 합니다.
3. Repository 구현
@Repository
class BulkRepository(
private val jdbcTemplate: JdbcTemplate,
private val objectMapper: ObjectMapper,
) {
fun updateMembers(memberList: List<MemberEntity>) {
val sql = "UPDATE member SET status = ? updated_at = ? WHERE member_id = ?"
jdbcTemplate.batchUpdate(sql, object : BatchPreparedStatementSetter {
override fun setValues(ps: PreparedStatement, i: Int) {
val entity = memberList[i]
ps.setString(1, entity.status)
ps.setTimestamp(2, Timestamp.valueOf(entity.updatedAt))
ps.setLong(3, entity.memberId)
}
override fun getBatchSize(): Int = memberList.size
})
}
}
마치며
이번 성능 개선을 통해 약 18분이 걸리던 처리 시간을 약 4분으로 단축할 수 있었습니다.
물론 이 해결방법이 모든 상황에 최적의 답은 아닐 수 있습니다.
하지만 비슷한 성능 이슈로 고민하시는 분들께 저의 경험이 하나의 참고사례가 되었으면 합니다.
그리고 더 좋은 방법이 있다면, 이 방법이 최선이 아니라는 것을 알고 계신다면 알려주세요ㅎㅎ
p.s. Bulk UPDATE 작업 시 WHERE IN 절을 활용하는 방법도 더 좋은 성능을 낼 수 있는 대안이 될 수 있다고 합니다.
(저의 경우에는 각 ROW 별 업데이트를 했어야 했으므로 활용하진 않았습니다.)