개요

springboot 에서 jdbcTemplate 을 쓰면서, insert/upsert/update 수행 시 어느정도 성능차이가 발생하는지 궁금했다. 사실 성능의 순서는 insert -> upsert -> update 로 예상은 되지만 그래도 데이터 사이즈에 따라서 어느정도 될 지 간단히 비교해봤다.

 

환경은 로컬환경에서 mysql 이미지의 컨테이너를 올려서, 테스트코드로 롤백을 수행하지 않은채 진행했다.

  • mysql 8.0
  • mac m1
  • spring-boot-starter-data-jdbc 2.6.11

코드

person 테이블에  name, email 은 unique key 로 잡혀있는 상태이다. duplicate key 발생 시, 컬럼을 업데이트 칠 수 있기 때문.

@Repository
class PersonRepository(
    private val jdbcTemplate: JdbcTemplate
) {

    /**
     * insert
     */
    fun insertPersonBulk(persons: List<Person>) {
        val query = """
            INSERT INTO person 
                (`name`, `email`, `remark`,`created_date`, `created_time`) 
            VALUES 
                (?, ?, ?, ?, ?)
        """.trimIndent()


        persons.chunked(BATCH_SIZE).forEach { chunkPersons ->

            jdbcTemplate.batchUpdate(query, object: BatchPreparedStatementSetter {
                override fun setValues(ps: PreparedStatement, i: Int) {
                    val person = chunkPersons[i]

                    ps.setString(1, person.name)
                    ps.setString(2, person.email)
                    ps.setString(3, person.remark)
                    ps.setDate(4, java.sql.Date.valueOf(LocalDate.now()))
                    ps.setTime(5, java.sql.Time.valueOf(LocalTime.now()))
                }

                override fun getBatchSize(): Int {
                    return chunkPersons.size
                }
            })
        }
    }

    /**
     * key duplicate -> update or insert
     */
    fun upsertPersonBulk(persons: List<Person>) {
        val query = """
            INSERT INTO person 
                (`name`, `email`, `remark`,`created_date`, `created_time`) 
            VALUES 
                (?, ?, ?, ?, ?) 
            ON DUPLICATE KEY UPDATE remark = ?
        """.trimIndent()


        persons.chunked(BATCH_SIZE).forEach { chunkPersons ->

            jdbcTemplate.batchUpdate(query, object: BatchPreparedStatementSetter {
                override fun setValues(ps: PreparedStatement, i: Int) {
                    val person = chunkPersons[i]

                    ps.setString(1, person.name)
                    ps.setString(2, person.email)
                    ps.setString(3, person.remark)
                    ps.setDate(4, java.sql.Date.valueOf(LocalDate.now()))
                    ps.setTime(5, java.sql.Time.valueOf(LocalTime.now()))

                    // key 충돌 시, 입력되는 리마크
                    ps.setString(6, person.remark)
                }

                override fun getBatchSize(): Int {
                    return chunkPersons.size
                }
            })
        }
    }

    /**
     * update
     */
    fun updatePersonBulk(persons: List<Person>) {
        val query = """
            UPDATE 
                person 
            SET 
                remark = ?, created_date = ?, created_time = ? 
            WHERE 
                name = ? AND email = ?
        """.trimIndent()


        persons.chunked(BATCH_SIZE).forEach { chunkPersons ->

            jdbcTemplate.batchUpdate(query, object: BatchPreparedStatementSetter {
                override fun setValues(ps: PreparedStatement, i: Int) {
                    val person = chunkPersons[i]

                    // update 되는 값
                    ps.setString(1, person.remark)
                    ps.setDate(2, java.sql.Date.valueOf(LocalDate.now()))
                    ps.setTime(3, java.sql.Time.valueOf(LocalTime.now()))

                    ps.setString(4, person.name)
                    ps.setString(5, person.email)
                }

                override fun getBatchSize(): Int {
                    return chunkPersons.size
                }
            })
        }
    }

    fun deleteAll(): Boolean {
        val query = """
            DELETE FROM person
        """.trimIndent()

        return jdbcTemplate.update(query) >= 1
    }

    companion object {
        private const val BATCH_SIZE = 3000
    }
}

 

결과

5000건

  • insert : 4초
  • upsert : 8초
  • update : 12초

100000건

  • insert : 92초
  • upsert : 205초
  • update : 297초

 

결과는 실제 서버가 돌아가는 환경에 따라 달라질 수 있기에, 참고로만 보면 좋을 것 같다.

Posted by doubler
,