JPA Optimization

JPA

  • 하나의 애플리케이션을 만들기 위해 수많은 기술들을 사용하고있지만 여전히 DBMS는 강력한 SPOF
  • DB Query 를 한번 추상화한 JPA 와 JPA 를 또 추상화한 spring data jpa 사용중
  • JPA 로 인한 첫번째 이슈는 예상치 못한 쿼리가 발생하는 것이며
  • 두번째 이슈는 예상치 못한 쿼리가 많이 발생하는 것
  • DB 성능 이슈는 대부분 SELECT 쿼리에서 발생
  • 한방쿼리의 부재

Readonly Transaction

  • readonly transaction에 대해서는 readonly 명시
  • @Transactional(readonly = true)
  • default 는 false
  • dirty check 를 안함
  • dirty check 를 안하니까 스냅샷 저장도 안하므로 GC 에도 이로움
  • flush 에서도 최적화가능 (readonly 이므로 flush 시 날아갈 쿼리가 없음)
  • a noticeable improvement on large object trees (spring-data-jpa reference)

Using DTO projection

  • 엔티티 조회는 필연적으로 모든 컬럼을 조회
  • DTO projection 을 이용해서 필요한 컬럼만 조회함으로써 조회성능 향상 가능
  • 다만 이 방식을 사용할수록 쿼리와 화면이 강결합될 수 있으므로 사용 여부를 잘 판단해야할것으로 보임
  • test: houseNativeJpaRepository

OSIV

  • 트랜잭션 종료 후에도 DB 커넥션을 갖고있어 proxy 가 동작 할수있게 함
  • 커넥션을 너무 오래 들고있기때문에 비용이 큼
  • spring 기본설정은 OSIV 를 켜두므로 꺼주는게 좋음
  • 명시적 설정이 없으면 warning 로그를 출력하며 명시적설정을 넣어주면 로그는 출력되지않음
  • boot 2.0 때 기본값 변경을 심각히 논의하였으나 beginner 들에 대한 허들로 변경되지않음(실제 PR 까지 올라갔으나 그냥 close)

Batch Insert

  • 기본적으로 배치처리를 지원하지않음
  • hibernate.jdbc.batch_size 옵션을 이용하여 jdbc batch 옵션을 켜도록 할 수 있음
  • 일반적인 JPA 쿼리로그로는 배치 처리가 정상적으로 확인됐는지 알 수 없음
  • logging.level.org.hibernate.engine.jdbc.batch.internal.BatchingBatch: debug
  • Proxy Datasource 를 구현해도됨
  • hibernate.order_inserts 옵션을 이용해 배치처리 최적화 가능
  • test: aloneSpringDataRepository

Batch Insert

  • auto_increament 를 사용하고있다면 JPA 는 JDBC batch 를 사용하지 못함
  • Batch Insert 를 해야한다면 깔끔하게 JPA 포기하고 spring-data-jdbc 등을 이용해 네이티브 insert 이용
  • JPA 이외의 기술을 이용할때는 persistence context 랑 무관해지므로 이에 유의해야함
  • DBMS 에는 없는 JPA 에만 있는 join 방식
  • 연관관계가 있는 엔티티들을 모두 조회하게됨
  • test: jobNativeJpaRepository

Join fetch

Fetching strategy - xToOne

  • 연관관계에 대해 모두 LAZY loading 사용
  • xToOne 은 EAGER, xToMany 는 LAZY 가 default
  • xToOne 은 fetch type 이 EAGER 일 경우 JPA 가 스스로 join 쿼리를 날려줌(그럼에도 LAZY 를 해야할까?)
  • test: jobNativeJpaRepository
  • EAGER fetching is almost always a bad choice.(hibernate reference)
  • LAZY associations can be fetched eagerly, EAGER associations cannot be fetched lazily (vlad mihalcea, hibernate commiter)
  • EAGER 는 N+1 문제의 불씨
  • 애플리케이션 규모가 커질수록 EAGER는 언제 의도하지않은 쿼리가 날아갈지 예측하기 어려움
  • (test: jobNativeJpaRepository)
  • fetch 전략을 LAZY 로 지정하고 필요한 연관관계에 대해서는 join fetch 로 명시적 쿼리
  • spring data jpa 에서도 join fetch 사용 가능
  • (test: jobSpringDataRepository)

Fetching strategy - xToOne

Fetching strategy - xToMany

  • xToMany 에서도 EAGER 로 하면 join 쿼리가 날아가긴 함
  • xToMany 관계에서도 join fetch 가능
  • test: personNativeJpaRepository

Fetching strategy - xToMany

  • xToMany 매핑이 2개 이상인경우엔?
  • test: personNativeJpaRepository

Fetching strategy - xToMany

  • paging 처리할땐?
  • test: personSpringJpaRepository

default_batch_fetch_size

  • hibernate option 중 default_batch_fetch_size 존재
  • spring.jpa.properties.hibernate.default_batch_fetch_size=10
  • 글로벌하게 설정하고싶지않다면 엔티티에 @BatchSize 사용

default_batch_fetch_size

  • batch_fetch_style 옵션이 hibernate 4.2 에 추가
  • default_batch_fetch_size 옵션은 batch_fetch_style 옵션의 영향을 받음
  • batch_fetch_style 옵션의 default 값은 legacy
  • legacy 옵션은 default_batch_fetch_size 값을 분할해서 배치 크기를 저장해둠(1~10까지는 모두 저장)
  • dynamic 은 배치크기 무시하고 실제 파라미터 개수대로 작동
  • padded 는 패딩값을 넣어 파라미터개수보다 한단계 큰 배치크기를 사용

정리

  • Readonly transaction 은 Readonly 명시
  • 복잡한 조회 쿼리는 DTO 사용으로 필요한 컬럼만 조회
  • OSIV 는 off
  • MySQL auto_increament 를 쓰고있다면 batch insert 는 그냥 네이티브 쿼리 사용
  • 연관관계 상관없이 fetchType은 LAZY 사용
  • OneToOne, ManyToOne 은 join fetch 로 조회
  • OneToMany 는 @BatchSize 를 이용해 in statement 사용

참고자료

JPA 최적화

By changyong

JPA 최적화

JPA 최적화 발표 자료

  • 411