모르지 않다는 것은 아는것과 다르다.

Database

Spring Data Jpa Bulk Insert

채마스 2022. 2. 27. 02:00

개요

  • 업무를 진행하면서 대량의 데이터를 대량으로 생성해서 일괄로 저장할 일이 있었다. 개발을 진행하는 과정에서 데이터를 하나하나 insert 하다보니 성능적인 이슈가 있었고, bulk insert와 같은 키워드로 구글링을 해보니 Hibernate Batch Insert라는 내용을 찾을 수 있었다.





batch insert 란?

  • batch insert 는 대량의 SQL Statement 를 하나의 구문으로 처리할 수 있는 방법입니다.
  • 아래와 같이 SQL 저장소에는 등록 쿼리가 2건이 저장된다.

  • 트랜잭션을 커밋 하면 엔티티 매니저는 우선 영속성 컨텍스트를 플러시 한다.

  • 플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업인데 이때 등록, 수정, 삭제한 엔티티를 데이터베이스에 반영한다.
  • 이렇게 동시에 2개의 insert 문을 1번에 처리할 수 있다.
  • 이러한 부분은 JPA 내부적으로 이루어지기 때문에 사용하는 코드에서는 코드의 변경 없이 이러한 작업들이 가능하다.
  • 이렇게 되면 아래와 같이 여러개의 구문을 여러 번 network 를 통해 보내는 것이 아니라 합쳐서 1개로 보내기에 성능 개선을 할 수 있다.
  • 예시는 아래와 같다.

  • 위와 같이 쿼리문이 단건으로 여러번 나가는 것을 확인할 수 있다.
  • 만약 itemRepository.save(newItem); 이런식으로 단건으로 처리하면 위와 같이 단건으로 처리된다.

  • 위와 같이 1개의 쿼리문으로 묶어서 보낼 수 있다.





application.yml 설정

spring:
  jpa:
    properties:
      hibernate:
        jdbc.batch_size: 100
        order_inserts: true
        order_updates: true
  datasource:
    hikari:
      data-source-properties:
        rewriteBatchedStatements: true
  • batch_size: Hibernate 가 최대 몇 개의 statement 를 묶어서 처리할지 지정한다.
    • 만약 batch_size 를 100이라고 지정한 뒤 1000개의 insert statement 를 호출한다면, 100개씩 10번의 insert 가 발생하게 된다.
  • rewriteBatchedStatements: true -> 여러개의 insert 쿼리를 하나의 쿼리로 합쳐준다.
  • order_inserts: true, order_updates: true -> update, insert 문의 실행 순서를 정렬해주는 옵션





코드 예시

  • 단건 저장
    public void saveStudents() {
        List<Student> list = new ArrayList<Student>() {
            {
                for (int i = 0 ; i < 200000 ; i++) {
                    Student newStudent = new Student();
                    studentRepository.save(newStudent);

                }
            }
        };
        studentRepository.saveAll(list);
    }
  • 위와 같은 코드는 단건 쿼리가 반복적으로 수행되기 대문에 성능적인 문제가 발생할 수 있다.
     public void saveStudents() {
        List<Student> studentList = new ArrayList<Student>() {
            {
                for (int i = 0 ; i < 500 ; i++) {
                    Student newStudent = new Student();
                    studentList.add(newStudent);
                }
            }
        };
        studentRepository.saveAll(studentList);
    }
  • batch insert 관련 설정을해주고 saveAll 로 일괄 저장을 하면 1번의 쿼리로 insert 할 수 있어서 성능적인 이점을 가져갈 수 있다.





pooled-lo optimizer를 적용

  • Id 를 채번하는 방식으로도 성능을 개선시킬 수 있다.
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "test-sequence-generator")
    @GenericGenerator(
            name = "test-sequence-generator",
            strategy = "sequence",
            parameters = {
                    @Parameter(name = SequenceStyleGenerator.SEQUENCE_PARAM, value = SequenceStyleGenerator.DEF_SEQUENCE_NAME),
                    @Parameter(name = SequenceStyleGenerator.INITIAL_PARAM, value = "1"),
                    @Parameter(name = SequenceStyleGenerator.INCREMENT_PARAM, value = "1000"),
                    @Parameter(name = AvailableSettings.PREFERRED_POOLED_OPTIMIZER, value = "pooled-lo")
            }
    )
    private Long id;
  • 한번에 일정 범위만큼 채번을 하여 메모리에 저장하여 사용하는 방식이다.
  • 위와 같이 설정하면 (update hibernate_sequence set next_val + 1000 where next_val = ?) 가 실행된다.
  • optimizer를 사용하면 1000개의 entity를 insert하는데 1번의 hibernate_sequence에 대한 쿼리가 실행되어 매우 효율적으로 ID채번이 가능합니다.





주의할 점

  • JPA에서 ID 채번 방식을 GenerateType.IDENDITY 사용 시 batch insert를 할 수 없다
  • insert 할때, select last_insert_id() 쿼리를 실행하여 다음에 insert할 ID를 채번해오기 때문에 batch insert를 막는다.
  • 그렇기 때문에 batch insert를 사용하기 위해선 ID 채번방식을 GenerateType.SEQUENCE나 GenerateType.TABLE을 사용해야 합니다.




REFERENCES

'Database' 카테고리의 다른 글

쿼리연습 (겹치는 날짜 검사하기)  (0) 2023.04.07
SQL  (0) 2022.05.30
프로시저  (0) 2022.02.27
트랜잭션 격리수준(Isolation level)  (0) 2022.02.27
조인  (0) 2022.02.27