• 카테고리

    질문 & 답변
  • 세부 분야

    백엔드

  • 해결 여부

    해결됨

multi-value insert 처리에 대하여 질문 드립니다.

20.11.12 17:55 작성 조회수 2.35k

0

영한님 안녕하세요.

Spring Data JPA 로 리파지토리를 만들고 테스트하다가 나온 사항에 대해 궁금증이 있어 문의를 드립니다.

질문사항은 다음과 같습니다.

ㅇ 목적: 여러 개의 row를 한 번에 insert할 때의 성능을 최적화하려고 함 (여러 개의 row를 넣는 테이블의 pk는 직접 입력을 받아 넣는 id 형식입니다.)

ㅇ 구현 의도: multi-value insert (insert into table values(...), (...), ... , (...) )

ㅇ 시도

-  JpaRepository의 saveAll() 메서드를 사용 (MySQL의 경우 multi-value insert 시 jdbc 옵션으로 rewriteBatchedStatements=true를, PostgreSQL의 경우 reWriteBatchedInserts=true를 주면 되는 것으로 알고 있습니다. h2 DB는 그런 것이 있는지 모르겠네요. 일단 format_sql=true로 했을 때 남겨진 로그로는 해당 내역을 확인하기 힘들었습니다.) 

- saveAll() 시 isNew 필드를 통해 persist, merge 분기를 타기 때문에 성능에 이슈가 있을 것으로 보고, 부가적인 select문 실행을 막기 위해 isNew 필드값을 아래와 같이 true로 만들었습니다.

@Transient
private boolean isNew = true;

@Override
public ClassId getId() {
return ClassId.builder()
.pk1(pk1)
.pk2(pk2)
.pk3(pk3)
.pk4(pk4)
.pk5(pk5)
.build();
}

@Override
public boolean isNew() {
return true;
}

@PrePersist
@PostLoad
public void markNotNew(){
isNew = false;
}

- bulk insert를 위한 batch_size 등의 옵션 추가 (추가한 옵션은 spring.jpa.properties 하위입니다.)

jdbc:
batch_size: 1000
batch_versioned_data: true
order_inserts: true
order_updates: true

ㅇ 의문점

1. 일단, 찍히는 sql이 insert into table values(...), (...), ... , (...)  과 같이 찍히지 않습니다. 과연 bulk로 모아서 실행이 되는 것인지가 궁금합니다. 물론 batch_size가 0일때보다 200일 때의 성능 개선이 있긴 한데 약간 미미하다는 생각이 들어서요...

2. em.persist를 n 번 수행한 뒤 flush한 결과보다 성능이 못한데, 이것이 무엇 때문에 나오는 결과인지 궁금합니다. (SimpleJpaRepository.class를 보니 saveAll 함수 내부 로직에서 save를 n 번 호출하고 save 함수 내에서 isNew를 체크하는데 여기서 오는 차이인지..)

3. 원래 100000 정도의 데이터를 insert 할 시에 이 정도의 성능이 나오는 것인지 궁금합니다. (테스트 결과는 아래에 있습니다.)

4. batch_size마다 saveAll()를 해 주고 리스트를 초기화 시켜주는 것이 성능상으로 의미가 있을 지 궁금합니다. (아래 성능 테스트에서는 리스트에 꽉 채운 뒤에 마지막에 saveAll을 호출했었습니다. 궁금하여 batch_size마다 saveAll을 호출하고 리스트를 초기화 시켰보았지만 시간 차이가 거의 나지 않았습니다. 제가 테스트를 잘못한 것인지...)
참고 링크: https://persistencelayer.wixsite.com/springboot-hibernate/post/the-best-way-to-batch-inserts-via-saveall-iterable-s-entities

성능 테스트

  • data size = 100000
    • 1. batch_size = 0
      • em.persist 사용: 6807 ms
      • JpaRepository.saveAll() 사용: 7861 ms
    • 2. batch_size = 200
      • em.persist 사용: 5547 ms
      • JpaRepository.saveAll() 사용: 7187 ms
    • 3. batch_size = 500
      • em.persist 사용: 5452 ms
      • JpaRepository.saveAll() 사용: 6489 ms
    • 4. batch_size = 1000
      • em.persist 사용: 5684 ms (배치 사이즈가 증가했음에도 더 오래 걸림)
      • JpaRepository.saveAll() 사용: 6360 ms
    • 5. batch_size = 5000
      • em.persist 사용: 5815 ms (배치 사이즈가 증가했음에도 더 오래 걸림)
      • JpaRepository.saveAll() 사용: 6659 ms (배치 사이즈가 증가했음에도 더 오래 걸림)

보통 영한님은 여러 row를 한 번에 insert하려고 할 때 어떻게 구현을 하시는지 궁금합니다.

답변 부탁드리겠습니다. 긴 내용 읽어주셔서 감사합니다 :)

답변 3

·

답변을 작성해보세요.

1

영한님 친절한 답변 감사합니다.

batch_size의 옵션은 말씀하신 것처럼 spring.jpa.properties.hibernate 하위로 잡았는데, 질문 드릴 때 제가 잘못 썼네요 ㅎㅎ

참고해서 계속 개발해보도록 하겠습니다.

감사합니다:)

0

네 화이팅^^!

0

안녕하세요. KyungWon Park님

1. 먼저 하이버네이트가 제공하는 벌크 insert는 말씀하신 것 처럼 쿼리문장을 multi value 하나로 만들어서 보내는 방식이 아닙니다. 여러 쿼리를 모아서 한번에 보내는 방식입니다. (따라서 쿼리 문장이 변하지는 않습니다. 로그 레벨을 낮추면 로그에서는 벌크로 입력했다고 나타납니다.)

2. spring.jpa.properties 하위가 아니라 spring.jpa.properties.hibernate 하위로 잡으셔야 될꺼에요^^

3. 테스트를 어떻게 하셨는지 구체적으로 보지는 못했지만, 트랜잭션 범위도 매우 중요합니다. 한 트랜잭션 안에서 처리하셔야 합니다.

4. saveAll도 결국 em.persist() 호출이기 때문에 isNew만 만족하면 차이는 없습니다.

Q: 원래 100000 정도의 데이터를 insert 할 시에 이 정도의 성능이 나오는 것인지 궁금합니다. (테스트 결과는 아래에 있습니다.)

-> 이 부분은 운영 시스템마다 다릅니다. 시스템 장비와 row길이, 수에 따라서 다른 부분이어서, 단순한 측정은 어렵습니다. 또 테스트 환경에 따라서 다를 수도 있습니다. 로컬 PC에서 하면 영향이 매우 적을 수 있습니다. 제가 있는 팀에서 테스트 했을 때는 데이터가 매우 많을 때 batch_size를 사용해서 절반정도 시간을 단축했습니다.

Q: 보통 영한님은 여러 row를 한 번에 insert하려고 할 때 어떻게 구현을 하시는지 궁금합니다.

-> 저는 정말 대량의 insert를 해야하는 상황이 아니면, 그냥 한 트랜잭션안에서 수백 ~ 수천 커밋을 처리하는 정도로 마무리 합니다. 사실 이런상황이 생각보다 많지는 않습니다. 그리고 이렇게 하면 대부분 성능 요구사항을 만족합니다. 그런데 여기서 더 최적화를 해야 하는 상황이 오면 그때 jdbc_batch를 사용합니다.

감사합니다.

채널톡 아이콘