[JPA] 대량의 데이터 일괄 DELETE 쿼리 개선

김상현·2022년 12월 28일
0

JPA

목록 보기
2/3
post-thumbnail

DELETE 쿼리 개선


0. 발생한 문제


장바구니에 존재하는 상품을 선택하여 주문하기 버튼을 클릭하면, 해당 상품을 주문 상태로 변경하고 장바구니에서 해당 상품들을 제거하는 것이었다.

문제는 장바구니에서 해당 상품들을 제거할 때 발생했다.

장바구니에서 2개 이상의 상품을 선택하여 주문하기 버튼을 클릭하였을 때 주문 서비스를 수행한 후 장바구니에서 해당 상품으들을 제거할 때 개별로 delete 쿼리가 발생했다.

🧷 delete 쿼리

delete 
  from
    cart_item 
  where
    cart_item_id=68
    
delete 
  from
    cart_item 
  where
    cart_item_id=69
    
delete 
  from
    cart_item 
  where
    cart_item_id=70

1. 문제 해결 과정


처음 접근한 방식은 Data JPA에서 제공하는 deleteById() 기능을 이용하였다.

🧷 deleteById

@Transactional
public void cancelCartItems(List<Long> cartItemIds) {
    cartItemIds.forEach(id -> cartItemRepository.deleteById(id));
}

하위 엔티티를 로딩할때 한번에 상위 엔티티 ID를 지정한 숫자만큼 in Query로 로딩해주는 default_batch_fetch_size 값을 100으로 설정해 두었기 때문에 한번의 Transactional 에서 발생한 쿼리는 모두 in Query로 처리되는 것으로 착각하고 있었다.

delete 쿼리가 여러번 발생한 이유는 반복문이 한번 처리될 때마다 한번씩 쿼리를 발생시키기 때문인 것 같다.

forEach() 와 같은 반복문 대신 한번에 값을 처리해줄 방법이 필요했고, Data JPA에서 여러개의 id를 받아 delete를 처리해주는 deleteAllById() 기능을 적용해보았다.

🧷 deleteAllById

@Transactional
public void cancelCartItems(List<Long> cartItemIds) {
        cartItemRepository.deleteAllById(cartItemIds);
}

In Query 를 통해 한번에 처리될 줄 알았지만 deleteById() 와 마찬가지로 여러번의 delete 쿼리가 발생했다.
Data JPA에서 제공하는 기능이기 때문에 분명히 최적화가 되어 제공될 것이라는 나의 생각이 산산조각 나버렸다.


2. 해결 방법


📒 문제 해결에 도움을 준 고마운 블로그 : JPA에서 대량의 데이터를 삭제할때 주의해야할 점

예상한대로 Data JPA에서 제공하는 deleteAllById() 메서드는 단건 삭제가 기본값이었다.
아래는 참고한 블로그에서 일부 발췌한 내용이다.

여기까지 결과로 알 수 있는 것은 JpaRepository에서 제공하는 deleteByXXX 등의 메소드를 이용하는 삭제는
단건이 아닌 여러건을 삭제하더라도 먼저 조회를 하고 그 결과로 얻은 엔티티 데이터를 1건씩 삭제한다는 것입니다.
즉, 제가 만약 1억건 중 50만건을 삭제한다고 하면 50만건을 먼저 조회후 건건으로 삭제한다는 것입니다.

향로님의 테스트 코드를 통해 deleteByXXX 의 메소드들이 단건으로 삭제되는 것을 확인할 수 있었다.
deleteByXXX 의 메소드의 작동 방식을 확인하는 것만으로 끝나지 않고 직접 삭제 쿼리를 작성하라는 해결 방법까지 제시해 주었다.

🧷 deleteAllByIds

@Modifying
@Query("delete from CartItem c where c.id in :ids")
void deleteAllByIds(@Param("ids") List<Long> ids);

JPA Repository에 deleteAllByIds() 라는 인터페이스를 생성한 후 @Query 를 통해 직접 삭제 쿼리를 작성하였다.
@Modifying 은 MDL 쿼리가 발생할 때 적용해주는 어노테이션이다.

🧷 cancelCartItems

@Transactional
public void cancelCartItems(List<Long> cartItemIds) {
    cartItemRepository.deleteAllByIds(cartItemIds);
}

직접 작성한 삭제쿼리를 CartService 에 적용한 후 테스트를 진행해본 결과 in Query 를 통해 일괄 삭제 쿼리가 발생하는 것을 확인할 수 있었다.

🧷 개선된 delete 쿼리

delete 
from
    cart_item 
where
    cart_item_id in (68, 69, 70)

3. 고찰


스프링이 제공해 주는 기능들이 워낙 막강하다 보니 항상 최적의 결과를 제공해 줄 것이라는 착각에 빠져있었던 것 같다.
직접 삭제 쿼리를 작성하는 방법 이외에 더 올바른 방법이 있을 것 같다는 생각이 들지만 오늘은 하나의 기능을 더 개선했고 공부했다는 것에 만족해야 겠다.

profile
목적 있는 글쓰기

1개의 댓글

comment-user-thumbnail
2023년 12월 12일

감사합니당!

답글 달기