본문 바로가기
프로젝트/FitTrip

[트러블슈팅] JPA의 deleteAll() 대신 IN을 사용한 성능 최적화

by 진꿈청 2024. 6. 25.

📄 Describe

작업 대상

지금까지 별 생각없이 JPA의 일 대 다 관계, 엔티티들에 관해 JPAdeleteAll()를 메소드를 사용했었다.
하지만, API 테스트 중 로그의 쿼리를 확인하니 관련있는 엔티티 개수만큼 delete 쿼리가 나가는 것을 확인했다.

 

FitTrip의 커뮤니티 서비스 기준 포럼 : 파일 -> 일 : 대 관계애서 포럼 한개를 삭제했을 때 파일의 개수만큼 delete 쿼리가 나간 것이다.
이는 만약 한 포럼에 수십, 수백개의 파일이 존재한다면 쿼리 성능에 문제가 있을 수 있겠다는 생각이 들었다.

 

따라서, 단순하게 JPA의 deleteAll()를 사용하는 것이 아닌 IN을 사용하여 배치 삭제를 해야겠다는 생각이 들었다.

 

 

deleteAll() 예시 사진

image

✅ Tasks

작업 세부 내용

위에서 설명한 것처럼 기존 JPA deleteAll() 방식 대신에 IN을 사용한 방법으로 새롭게 구현한 과정이다.

 

기존 코드

private void fileDelete(List<File> files) {
        files.stream()
                .map(File::getFileUrl)
                .forEach(fileUploadService::delete);

        List<Long> fileIds = files.stream()
                .map(File::getId) 
                .toList();

        fileRepository.deleteAll();
    }

 

변경한 코드

private void fileDelete(List<File> files) {
        files.stream()
                .map(File::getFileUrl)
                .forEach(fileUploadService::delete);

        List<Long> fileIds = files.stream()
                .map(File::getId) 
                .toList();

        fileRepository.deleteAllByIdIn(fileIds);
    }
public interface FileRepository extends JpaRepository<File, Long> {

    @Modifying
    @Query("delete from File f WHERE f.id IN :ids")
    void deleteAllByIdIn(@Param("ids") List<Long> ids);

}

 

하지만, 이렇게 했을 때 아래와 같은 오류가 발생했다.

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

 

다른 트랜잭션에서 해당 로우가 이미 수정되거나 삭제되었다는 오류가 발생했다.
이런 오류가 뜬 원인을 생각하다가 CASCADE가 혹시 문제가 되지 않을까라는 생각이 들었다.

 

나는 포럼이 삭제되었을 때 File도 같이 삭제되기를 원했기 때문에 orphanRemoval=true 설정을 사용했었다.
하지만, 해당 설정으로 인해 IN으로도 삭제가 되고 orphanRemoval에 의해 Forum에 의한 삭제도 시도한다.
따라서, 위 오류의 원인이 해당 설정이라는 생각이 들었고 다음과 같이 수정했다.

 

기존 코드

    @OneToMany(mappedBy = "forum", orphanRemoval = true)
    @OneToMany(mappedBy = "forum")
    private List<File> files = new ArrayList<>();

 

변경한 코드

//    @OneToMany(mappedBy = "forum", orphanRemoval = true)
    @OneToMany(mappedBy = "forum")
    private List<File> files = new ArrayList<>();

이렇게 설정하니 문제없이 IN으로 인한 Batch 삭제가 가능해졌다.

 

🙋🏻 More

나는 직접 @Query를 사용하여 배치 삭제를 위한 코드를 작성했지만, JPA에 이미 해당 메소드가 존재한다는 것을 깨달았다.

  • deleteAllInBatch()
  • deleteAllByIdInBatch()

 

 

참고 📢

 

만약, 팀원 분들이 deleteAll()를 사용하고 계셨다면 위의 두 메소드를 이용하는 것이 좋을 것 같습니다.
하지만, 실제 서비스에서는 문제가 생길 우려가 다분한 CASCADE 사용을 지양한다고 합니다.

 

첨부파일 사진 출처(FitTrip과는 무관)
https://github.com/haero77/Today-I-Learned/assets/65555299/ae33f36c-4129-49a1-8428-8d03d980079f