본문 바로가기
개발자 경험 & 팁

배치 처리

by DoRightting 2024. 11. 19.

1. 프로젝트 내 배치 처리 구현

1.1 구독 갱신 배치 처리

@Component
@RequiredArgsConstructor
public class SubscriptionScheduler {

    @Scheduled(cron = "0 0 0 * * *")  // 매일 자정
    @Transactional
    public void processSubscriptionRenewals() {
        log.info("Starting daily subscription renewal processing");
        LocalDateTime now = LocalDateTime.now();

        List<Subscription> dueSubscriptions = subscriptionRepository
            .findDueSubscriptions(now);
        log.info("Found {} subscriptions due for renewal", dueSubscriptions.size());

        for (Subscription subscription : dueSubscriptions) {
            try {
                processSubscriptionRenewal(subscription);
                log.info("Successfully processed renewal for subscription: {}",
                    subscription.getSubscriptionId());
            } catch (Exception e) {
                log.error("Failed to process renewal for subscription: {} - {}",
                    subscription.getSubscriptionId(), e.getMessage());
                handleRenewalFailure(subscription, e);
            }
        }
    }
}

1.2 결제 재시도 배치

@Service
public class PaymentRetryService {
    @Value("${payment.retry.max-attempts}")
    private int maxRetryAttempts;

    @Scheduled(fixedDelay = 4, timeUnit = TimeUnit.HOURS)
    @Transactional
    public void retryFailedPayments() {
        log.info("Starting failed payment retry processing");
        List<Subscription> failedSubscriptions = subscriptionRepository
            .findByStatus(SubscriptionStatus.PAYMENT_FAILED);

        for (Subscription subscription : failedSubscriptions) {
            String subscriptionId = subscription.getSubscriptionId();
            Long paymentId = subscription.getLastPayment().getPayment().getId();
            long retryCount = paymentLogRepository
                .countByPaymentIdAndLogType(paymentId, RETRY_LOG_TYPE);

            if (retryCount >= maxRetryAttempts) {
                handleMaxRetriesExceeded(subscription);
                continue;
            }

            try {
                processSubscriptionRenewal(subscription);
                recordPaymentLog(subscription, RETRY_LOG_TYPE, "Payment retry successful");
                notificationService.sendPaymentRetrySuccess(subscription);
            } catch (Exception e) {
                recordPaymentLog(subscription, RETRY_LOG_TYPE,
                    "Payment retry failed: " + e.getMessage());
                notificationService.sendPaymentFailure(subscription);
            }
        }
    }
}

프로젝트에서 배치 처리가 필요했던 부분

  1. 구독 갱신 처리
    • 일일 구독 갱신 처리
    • 대량의 구독 데이터 효율적 처리
  2. @Scheduled(cron = "0 0 0 * * *") public void processSubscriptionRenewals() { List<Subscription> dueSubscriptions = subscriptionRepository.findDueSubscriptions(LocalDateTime.now()); // 갱신 대상 구독 일괄 처리 }
  3. 결제 재시도
    • 실패한 결제 주기적 재시도
    • 결제 성공률 향상
  4. @Scheduled(fixedDelay = 4, timeUnit = TimeUnit.HOURS) public void retryFailedPayments() { List<Subscription> failedSubscriptions = subscriptionRepository.findByStatus(PAYMENT_FAILED); // 실패한 결제 일괄 재시도 }

배치 처리 예상 문제 해결전략

  1. 트랜잭션 관리
    • 개별 트랜잭션 처리
    • 실패 격리
  2. @Transactional public void processSubscriptionRenewal(Subscription subscription) { try { // 개별 구독 처리 processSubscriptionRenewal(subscription); } catch (Exception e) { // 실패 처리 handleRenewalFailure(subscription, e); // 다른 구독 처리는 계속 진행 } }
  3. 에러 처리 및 로깅
    • 상세한 로그 기록
    • 실패 추적 용이
  4. private void recordPaymentLog(Subscription subscription, String logType, String content) { PaymentLog log = PaymentLog.builder() .payment(subscription.getLastPayment().getPayment()) .logType(logType) .content(content) .createdAt(LocalDateTime.now()) .build(); paymentLogRepository.save(log); }

배치 처리의 성능 최적화

  1. 페이지네이션
    • 대량 데이터 처리시 메모리 관리
    • 처리 단위 최적화
  2. public List<Subscription> findDueSubscriptions(LocalDateTime date) { return subscriptionRepository .findByNextPaymentDateBeforeAndStatus( date, SubscriptionStatus.ACTIVE, PageRequest.of(0, 100) ); }
  3. 실행 시간 관리
    • 적절한 실행 주기 설정
    • 시스템 부하 분산
  4. @Scheduled(cron = "0 0 0 * * *") // 자정에 실행 @Scheduled(fixedDelay = 4, timeUnit = TimeUnit.HOURS) // 4시간 간격

배치 작업 모니터링 구현

  1. 로그 기록
    • 배치 진행 상황 추적
    • 성능 지표 수집
  2. log.info("Starting daily subscription renewal processing"); log.info("Found {} subscriptions due for renewal", dueSubscriptions.size()); // 개별 처리 결과 로깅 log.info("Successfully processed renewal for subscription: {}", subscription.getSubscriptionId());
  3. 실패 처리
    • 실패 건 추적
    • 운영팀 알림
  4. private void handleRenewalFailure(Subscription subscription, Exception e) { // 실패 로그 기록 log.error("Failed to process renewal: {}", e.getMessage()); // 알림 발송 notificationService.sendPaymentFailure(subscription); }

배치 작업의 재시도 정책 설계

  1. 단계적 재시도
    • 최대 재시도 횟수 제한
    • 점진적 재시도 간격
  2. @Value("${payment.retry.max-attempts}") private int maxRetryAttempts; if (retryCount >= maxRetryAttempts) { handleMaxRetriesExceeded(subscription); return; }
  3. 실패 처리
    • 최종 실패 처리
    • 사용자 통보
  4. private void handleMaxRetriesExceeded(Subscription subscription) { subscription.setStatus(SubscriptionStatus.EXPIRED); subscription.setEndDate(LocalDateTime.now()); subscriptionRepository.save(subscription); notificationService.sendSubscriptionExpired(subscription); }

배치 작업과 실시간 처리의 균형

  1. 리소스 관리
    • 적절한 실행 시간대 선택
    • 시스템 리소스 분배
  2. @Scheduled(cron = "0 0 2 * * *") // 새벽 2시 실행 public void processSubscriptionRenewals() { // 시스템 부하가 적은 시간대에 실행 }
  3. 우선순위 설정
    • 중요도에 따른 처리 순서
    • 실시간 처리와의 조화
  4. @Scheduled(fixedDelay = 4, timeUnit = TimeUnit.HOURS) public void retryFailedPayments() { // 긴급하지 않은 재시도는 주기적으로 처리 }

'개발자 경험 & 팁' 카테고리의 다른 글

RabbitMQ - 406 에러  (5) 2024.10.19
git pull 실패  (0) 2024.07.30
exception just for purpose of providing stack trace 오류  (0) 2024.07.23
MariaDB 설치 오류  (0) 2024.07.18