본문 바로가기

Back-End/JPA

JPA / ORM 개발시 성능 향상시키기

스프링 개발을 하면서 ORM을 일반적으로 사용한다. 그 이유는 아래와 같다고 생각한다.

  1. relational 은 Object-oriented 프로그래밍과 매치가 되지 않음
  2. 다양한 RDBMS SQL 사용법

하지만 ORM framework ( Hibernate, EclipseLink )를 사용하면 성능을 신경써야 한다. 프레임워크가 모두 해결해 줄것이라 생각했다가는 나중에 성능 향상을 위해 코드를 전부 수정하여야 한다.

 

1. 연관관계에서 기본 FetchMode를 사용하라.

JPA에서 기본 FetchMode는

  • One-to-One, Many-to-One : EAGER
  • One-to-Many, Many-to-Many : LAZY

이다. 다 알다시피, LAZY를 사용하면 query가 한번 더 날아가는 대신 지금 필요없는 데이터를 메모리에 올리지 않는다는 장점이 있다. 하지만 데이터가 무조건 필요한 경우는 EAGER로 사용하게 되는데, 이는 비즈니스 상황에 따라 조절하면 된다.

 

2. NEW operator를 사용해서 query를 써라.

SELECT 쿼리를 이용해서 데이터를 가지고 올 때, 사용하지도 않는 컬럼들을 가지고 와 메모리에 적재시키기도 하고 DTO를 이용하여 외부로 전송될 때 트래픽이 낭비되기도 한다. 이를 위해서 NEW operator 사용을 권장한다.

SELECT NEW Foo(f.field1, f.field2, f.field3)
FROM Foo f JOIN f.bar bar
WHERE f.field0 = :valueToField0 AND bar.id = :valueToBarId

 

3. 연관관계를 위해서는 JOIN FETCH를 사용해서 query를 써라.

연관관계가 걸린 entity를 가져올 때 LAZY이면 N+1번의 쿼리가 날아간다. 쿼리 호출 회수를 줄이기 위해 JOIN FETCH를 사용하면 EAGER로 모두 가져온다. 

select m
from Member m join fetch m.team

그렇다고 무조건 JOIN FETCH를 사용하는게 좋을까?

  • 컬렉션을 패치조인하면 페이징 API를 사용할 수 없다
  • 빠를 수도 있지만 사용하지 않는 엔티티를 모두 가지고 오기 때문에 오히려 성능이 떨어질 수 있다

4. SQL 디버깅 비활성화하기

운영환경에서는 SQL 선언문을 console에 봉여주지 않도록 설정하자. 간단하면서도 당연한 얘기지만 잊어서는 안 될 설정이다.

 

5. Hibernate Statistics를 이용하여 성능 이슈를 찾아라

테스트 환경에서는 보통 성능 이슈를 잘 발견하지 못한다. 왜냐하면 혼자 사용하고 대량의 트래픽이 아니기 때문이다.

이를 놓치지 않기 위해서 Hibernate Statistics를 사용하자

  • hibernate.generate_statistic = true
  • org.hibernate.stat : DEBUG

2019-03-03 20:28:52,484 DEBUG [org.hibernate.stat.internal.ConcurrentStatisticsImpl] (default task-1) HHH000117: HQL: Select p From Product p, time: 0ms, rows: 10

2019-03-03 20:28:52,484 INFO  [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] (default task-1) Session Metrics {

    8728028 nanoseconds spent acquiring 12 JDBC connections;

    295527 nanoseconds spent releasing 12 JDBC connections;

    12014439 nanoseconds spent preparing 21 JDBC statements;

    5622686 nanoseconds spent executing 21 JDBC statements;

    0 nanoseconds spent executing 0 JDBC batches;

    0 nanoseconds spent performing 0 L2C puts;

    0 nanoseconds spent performing 0 L2C hits;

    0 nanoseconds spent performing 0 L2C misses;

    403863 nanoseconds spent executing 1 flushes (flushing a total of 10 entities and 0 collections);

    25529864 nanoseconds spent executing 1 partial-flushes (flushing a total of 10 entities and 10 collections)

}

 

https://thoughts-on-java.org/how-to-activate-hibernate-statistics-to-analyze-performance-issues/

 

How to activate Hibernate Statistics to analyze performance issues - Thoughts on Java

Did you ever ask yourself why a server request took ages on the production system while your local test system was just fine? Well, we all had these situations and we will have several more of them in the future. In my experience, strange performance drops

thoughts-on-java.org

6. Slow query 개선하기

JPA 뿐만 아니라 심지어 JDBC에서도 발생하는 문제다. 이러한 문제는 JPQL이나 Criteria API를 더 볼 것이 아니라 Native Query를 이용해서 개선해 나가야 한다. 하지만, 이또한 Object[]로 값을 받는다는 단점은 가지고 있다. 물론 아래와 같이 할 수는 있다.

List<Author> results = this.em.createNativeQuery
	("SELECT a.id, a.firstName, a.lastName, a.version FROM Author a", Author.class)
    .getResultList();