[성능 개선] 스프링 배치 / JPQL 쿼리 작성하여 조회없이 삭제 + 대규모 데이터 세트에서 배치 단위로 삭제

최혜원·2024년 9월 22일
1

불필요한 데이터 처리

결제하지 않은 주문 목록 다음 주문할 때 삭제되도록 하였지만 → 다음 주문하지 않으면 저장된 주문데이터가 그대로 남아있는 문제가 있다.

더미 데이터 생성

주문 데이터 삭제하면 주문에 딸린 주문상품목록도 함께 삭제된다.
-> order가 100만개 - order item이 order당 3개로 300만개 총 400만 건에 대한 데이터 테스트

Spring Batch 구현

OrderServiceImpl

    @Override  //스케줄러로 주기적으로 삭제
    @Transactional // 주문 상태 ORDER 인 주문 목록 일괄 삭제
    public void deleteOrdersInBatches(OrderStatus status, int batchSize) {
        int deletedCount = 0;
        int batchDeleted;

        long startTime = System.currentTimeMillis();
        log.info("Starting deletion of orders with status {} in batches of {}", status, batchSize);

        try {
            do {
                batchDeleted = orderRepository.deleteBatchByStatus(status); // 주문 삭제
                deletedCount += batchDeleted;

                log.info("Deleted batch of {} orders", batchDeleted);
            } while (batchDeleted >= batchSize);

            // Record the end time
            long endTime = System.currentTimeMillis();
            long duration = endTime - startTime;

            log.info("Successfully deleted {} orders with status {}. Time taken: {} ms",
                deletedCount, status, duration);
        } catch (Exception e) {
            log.error("An error occurred while deleting orders: {}", e.getMessage());
            throw e;
        }
    }

OrderCleanUpConfig

/**
 * 주문 상태 ORDER 인 주문 목록 하루에 한 번씩 삭제
 */
@Configuration
@RequiredArgsConstructor
public class OrderCleanUpConfig {

    private final OrderService orderService;

    @Bean
    public Job orderCleanupJob(JobRepository jobRepository, Step orderCleanupStep) {
        return new JobBuilder("orderCleanupJob", jobRepository)
            .incrementer(new RunIdIncrementer())
            .start(orderCleanupStep)
            .build();
    }

    @JobScope
    @Bean
    public Step orderCleanupStep(JobRepository jobRepository,
        PlatformTransactionManager transactionManager,
        Tasklet orderCleanupTasklet) {
        return new StepBuilder("orderCleanupStep", jobRepository)
            .tasklet(orderCleanupTasklet, transactionManager)
            .build();
    }

    @StepScope
    @Bean
    public Tasklet orderCleanupTasklet() {
        return (contribution, chunkContext) -> {
            orderService.deleteOrdersInBatches(OrderStatus.ORDER, 100);
            return RepeatStatus.FINISHED;
        };
    }
}

OrderRepository

public interface OrderRepository extends JpaRepository<Order, Long> {

    @Modifying // ORDER 주문 목록 전체 삭제
    @Query("DELETE FROM Order o WHERE o.orderStatus = :status")
    int deleteBatchByStatus(@Param("status") OrderStatus status); // 삭제된 행 수 반환
 }

Scheduler로 Spring Batch Job을 실행

Scheduler (배치 작업 실행)

@Component
@RequiredArgsConstructor
public class Scheduler {

    private final Job orderCleanupJob;
    private final JobLauncher jobLauncher;

    //@Scheduled(cron = "0 */1 * * * *") // 1분마다
    @Scheduled(cron = "0 0 4 * * *") // 하루에 한번
    public void orderCleanupJobRun()
        throws JobInstanceAlreadyCompleteException,
        JobExecutionAlreadyRunningException,
        JobParametersInvalidException,
        JobRestartException {

        JobParameters jobParameters = new JobParametersBuilder()
            .addLong("requestTime", Instant.now().toEpochMilli())
            .toJobParameters();

        jobLauncher.run(orderCleanupJob, jobParameters);
    }
}

💥 삭제 시 조회 쿼리 삭제 개수만큼 생성되는 문제 발생

SpringDataJpa에서 deleteByxxx 등의 메소드 사용시
삭제 대상들을 전부 조회하는 쿼리가 1번 발생한다.
삭제 대상들은 1건씩 삭제된다.
• cascade = CascadeType.DELETE 으로 하위 엔티티와 관계가 맺어진 경우 하위 엔티티들도 1건씩 삭제가 진행된다.

💡 해결책 -> 직접 범위 조건의 삭제 쿼리를 생성

  @Modifying // ORDER 주문 목록 전체 삭제
  @Query("DELETE FROM Order o WHERE o.orderStatus = :status")
  int deleteBatchByStatus(@Param("status") OrderStatus status); // 삭제된 행 수 반환

⭐️ JPQL 쿼리를 작성하여 조회없이 삭제하도록 했다.(불필요한 조회 과정 제거)


배치 사이즈 설정

대규모 데이터 처리 시 발생할 수 있는 메모리 부하를 방지하기 위해 적절한 배치 사이즈를 설정하여 청크 단위로 삭제를 수행하도록 했다.

실행 시간 측정

배치 사이즈 적용 전

결과 : 98884ms

배치 사이즈(1000개씩) 적용 후

결과 : 85865ms

배치 사이즈(100개씩) 적용 후

결과 : 78692ms

=> 총 20.4% 감소

profile
어제보다 나은 오늘

0개의 댓글