일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- ORM
- CS
- Container
- 웹 서버
- CI/CD
- Java
- HTTP
- web server
- vm
- 스프링
- 배포
- 스프링 배치
- 컨테이너
- spring batch
- 스프링 부트
- 데이터베이스
- virtualization
- spring boot
- JPA
- 백엔드
- Spring
- 자바
- 가상화
- mysql
- computer science
- Spring Security
- 영속성 컨텍스트
- 도커
- spring cloud
- 스프링 시큐리티
- Today
- Total
개발 일기
[Spring Boot] 우아콘2020 - 수십억건에서 QUERYDSL 사용하기 감상문 본문
정리하게된 계기
필터링 같은 기능을 구현할때 QueryDSL을 사용하면 정말 좋을 것 같다는 생각이 들어 래퍼런스를 찾아볼 겸 유투브를 보고있다가 해당 영상을 보게됐다. 배민에서 주문, 결제, 할인에 대한 데이터가 각각 10억건, 13억건, 4억건이 될 정도로 정말 방대한 양의 데이터가 축적된 것을 확인할 수 있었다. 그래서 이러한 데이터가 1000만건에서 10억건이 되는 과정에서 이동욱 개발자님 께서 얻은 QueryDSL-JPA 개선에 대한 정보를 공유한 영상이였다.
처음엔 아무 생각없이 보다가 사소한 SQL문에서 생각해보지도 못한 성능 이슈를 겪을 수 있겠다라는 생각 그리고 QueryDSL을 쓸때 가독성을 증가 시킬 수 있는 방법 등 정말 다양한 인사이트를 제공하는 영상이였다. 그래서 이러한 것들을 잊고 싶지 않아 나중에 QueryDSL을 사용해야될 상황에서 다시 한번 상기 시키고 개발을 해야되겠다는 생각에 정리하게됐다.
1. 워밍업
Query DSL 사용 시 상속과 구현 지양
Query DSL을 사용할때마다 OrderRepositoryCustom을 상속받고 또 구현체인 OrderRepositoryimpl가 필요한데 이게 너무 과하다 생각될 수 있다. 그러한 경우 매번 Support를 상속받고 super 생성자에 Entity를 등록해야한다. 이러한 과정들이 불편하다고 생각될 수 있다.
그래서 꼭 무언가를 상속/구현 받지 않더라도, 그리고 꼭 특정 Entity를 지정하지 않더라도 Query DSL을 사용할 수 있는 방법을 고민한 결과 JPAQueryFactory만 생성자 주입을 받아 사용하면 Query DSL을 사용가능했다.
동적 쿼리 BooleanExpression
이번에는 내가 사용하고자 하는 이유인 동적 쿼리 처리이다.
BooleanBuilder로 처리를 해도 성능상 문제가 되진 않지만 필드가 너무 많아진 경우 if 문이 너무 길어지고 가독성이 떨어져서 BooleanExpression을 사용하여 처리한다.
메소드로 처리해서 null반환 시에 자동으로 조건절에서 제거된다.
2. 성능 개선 - Select
바로 Query dsl에서 exist 금지! -> 유 무 여부를 따지는 로직에서 적용시키자
SQL에서 특정 조건을 만족하는 row가 있냐 없냐로 하여 exist는 만족하는 row를 찾으면 바로 종료되고 count는 무조건 끝 row까지 탐색하기 때문이다.
그런데 Query dsl에서 exist가 실제 쿼리 상 count를 사용하기 때문에 성능 이슈가 발생할 수 있다.
그래서 Limit 1로 조회 제한하는 방법을 사용하는 것이 좋다.
그러나 이때 0이냐 아니냐로 판단하는게 아니라 null이냐 아니냐로 판단해야한다. 조건에 부합하는게 없으면 0이 아닌 null이기 때문이다.
Cross Join 회피
김영한님이 말씀해주신 내용과 같은 내용이다. 묵시적 조인말고 명시적 조인을 사용하라
이때 추가적으로 innerjoin, outerjoin 등 원하는 조인으로 지정해줄 수 잇는 것이다.
Entity 보다는 DTO를 우선
이것 또한 김영한 님이 API 성능 개선(실전 JPA2)에선가? 얘기해주신 부분이다.
조회 시에 엔티티로 조회해서 DTO로 변환하는 방식을 우선 시하는데 그때 페치 조인을 통해 쿼리 수를 최적화 할 수 있다고 했다.
그러나 위의 방식으로도 해결이 안되면? DTO 조회 방식을 사용하며 그래도 안되면 Native SQL 이나 JdbcTemplete을 권장했다.
이동욱님도 이 부분에 대해서 얘기하신 것 같다.
DTO 조회는 고강도 성능 개선이나 대량의 데이터 조회가 필요한 경우 성능 상 이점이 많다고 하셨다!
- 조회 컬럼 최소화 하기- as표현식으로 대체
- Select 컬럼에 Entity 자제
: 그렇게되면 아래와 같이 조회한 엔티티의 필요없는 컬럼도 합쳐져서 모든 컬럼이 조회된다.
또한 추가적으로 Customer와 @OneToOne 관계인 Shop이 매 건마다 조회된다.(OneToOne은 Lazy Loading이 안돼서 무조건 N+1 문제가 발생한다. 그래서 customer 조회시에 원투원 관계에 있는 shop은 한건한건마다 조회되게 된다. 그런데 이때 최악의 상황으로 shop과의 원투원 관계에 있는 엔티티가 있다면? 쿼리가 난리나는 것이다.(100배, 1000배의 쿼리가 수행된다고한다.)
또한 Distinct 사용시에 Select에 선언된 Entity의 컬럼 전체가 distinct 대상이 되므로 이를 위한 리소스가 소모되어 성능이 떨어진다.
- Group By 최적화
MySQL에서 Group By를 실행하면 Filesort가 필수로 발생한다. 이때 order buy null을 사용하면 이를 제거할 수 있다. 그러나 Query DSL에서는 또 이 order by null 문법을 지원하지 않는다. 그래서 조건 클래스를 생성하여 적용해줬다.
만약 정렬이 필요하며 조회 결과가 100건 이하라면, 애플리케이션에서 정렬하라.
DB보다 WAS의 비용이 저렴함.
단, 페이징에서는 order by null 불가
- 커버링 인덱스
: 페이징 성능을 향상시킬 수 있다.
그러나 JPQL은 from 절의 서브 쿼리를 지원하지 않는다.
Cluster Key(PK)를 커버링 인덱스로 빠르게 조회하고, 조호된 Key로 SELECT 컬럼들을 후속 조회한다.
3. 성능 개선 - Update/Insert
일괄 Update 최적화 - 무분별한 DirtyChecking X
보통 JPA를 많이 쓰다보면 더티 체킹을 많이 사용하게되는데 그러한 경우 1000건 10000건이 발생하더라도 그 엔티티를 찾아서 일일이 업데이트 시켜주게된다. 그렇게되면 오른쪽의 QueryDSL을 통해 한방에 때려버리는 update와 성능 차이가 엄청 나게된다.
이 부분은 내가 soft delete를 구현하면서 느껴 비록 QueryDSL은 아니지만 JPQL을 통해 일괄 Update로 수정했던 기억이 있다.
그러나 단점도 있다. 하이버네이트 캐시는 일괄 업데이트 시 캐시 갱신이 안되기 때문에 이런 경우에 업데이트 대상들에 대한 Cache Eviction이 필요하다고 한다.
즉, 실시간 비즈니스 처리 및 실시간 단건 처리 시 DirtyChecking을 사용하고 대량의 데이터를 일괄로 Update 처리 시에는 Querydsl.update를 사용하는 것이 유리하다.
Bulk Insert
-> EntityQL
'Back-End > Spring' 카테고리의 다른 글
[Spring Boot] 데이터 전송 객체 그리고 DTO를 class가 아닌 record로 선언한 이유 (0) | 2024.08.18 |
---|---|
[Spring Boot] JPA의 @Query, @Modifying 그리고 @Transactional (0) | 2024.08.16 |
[Spring Boot] Entity에서의 올바른 롬복(Lombok) 사용에 있어서 나의 생각 정리 (1) | 2024.06.03 |
[Spring Boot] orphanRemoval = true 고아 객체 관리 (1) | 2024.06.03 |
[Spring Boot] 연관관계 영속성 전이(CASCADE) - REMOVE, PERSIST, ALL을 중심으로 (1) | 2024.06.03 |