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 사용
참고자료
- vlad mihalcea blog ( https://vladmihalcea.com/ )
- vlad mihalcea slide share ( https://www.slideshare.net/VladMihalcea/highperformance-hibernate-jdkio-2018 )
- OSIV issue ( https://github.com/spring-projects/spring-boot/issues/7107 )
JPA 최적화
By changyong
JPA 최적화
JPA 최적화 발표 자료
- 411