개발자 경험 & 팁
배치 처리
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);
}
}
}
}
프로젝트에서 배치 처리가 필요했던 부분
- 구독 갱신 처리
- 일일 구독 갱신 처리
- 대량의 구독 데이터 효율적 처리
- @Scheduled(cron = "0 0 0 * * *") public void processSubscriptionRenewals() { List<Subscription> dueSubscriptions = subscriptionRepository.findDueSubscriptions(LocalDateTime.now()); // 갱신 대상 구독 일괄 처리 }
- 결제 재시도
- @Scheduled(fixedDelay = 4, timeUnit = TimeUnit.HOURS) public void retryFailedPayments() { List<Subscription> failedSubscriptions = subscriptionRepository.findByStatus(PAYMENT_FAILED); // 실패한 결제 일괄 재시도 }
배치 처리 예상 문제 해결전략
- 트랜잭션 관리
- @Transactional public void processSubscriptionRenewal(Subscription subscription) { try { // 개별 구독 처리 processSubscriptionRenewal(subscription); } catch (Exception e) { // 실패 처리 handleRenewalFailure(subscription, e); // 다른 구독 처리는 계속 진행 } }
- 에러 처리 및 로깅
- 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); }
배치 처리의 성능 최적화
- 페이지네이션
- 대량 데이터 처리시 메모리 관리
- 처리 단위 최적화
- public List<Subscription> findDueSubscriptions(LocalDateTime date) { return subscriptionRepository .findByNextPaymentDateBeforeAndStatus( date, SubscriptionStatus.ACTIVE, PageRequest.of(0, 100) ); }
- 실행 시간 관리
- @Scheduled(cron = "0 0 0 * * *") // 자정에 실행 @Scheduled(fixedDelay = 4, timeUnit = TimeUnit.HOURS) // 4시간 간격
배치 작업 모니터링 구현
- 로그 기록
- 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());
- 실패 처리
- private void handleRenewalFailure(Subscription subscription, Exception e) { // 실패 로그 기록 log.error("Failed to process renewal: {}", e.getMessage()); // 알림 발송 notificationService.sendPaymentFailure(subscription); }
배치 작업의 재시도 정책 설계
- 단계적 재시도
- @Value("${payment.retry.max-attempts}") private int maxRetryAttempts; if (retryCount >= maxRetryAttempts) { handleMaxRetriesExceeded(subscription); return; }
- 실패 처리
- private void handleMaxRetriesExceeded(Subscription subscription) { subscription.setStatus(SubscriptionStatus.EXPIRED); subscription.setEndDate(LocalDateTime.now()); subscriptionRepository.save(subscription); notificationService.sendSubscriptionExpired(subscription); }
배치 작업과 실시간 처리의 균형
- 리소스 관리
- @Scheduled(cron = "0 0 2 * * *") // 새벽 2시 실행 public void processSubscriptionRenewals() { // 시스템 부하가 적은 시간대에 실행 }
- 우선순위 설정
- 중요도에 따른 처리 순서
- 실시간 처리와의 조화
- @Scheduled(fixedDelay = 4, timeUnit = TimeUnit.HOURS) public void retryFailedPayments() { // 긴급하지 않은 재시도는 주기적으로 처리 }