[BE] Spring Batch 테크스팩 - 100-hours-a-week/2-hertz-wiki GitHub Wiki

배경 (Background)

  • 프로젝트 목표 (Objective):
    조직 내 매칭 결과를 AI가 자동으로 작성한 ‘튜닝 리포트’ 형태의 뉴스 콘텐츠로 가공하고, 해당 결과를 사용자들에게 정기적 알림으로 전달함으로써 서비스의 재미와 몰입도를 극대화함

  • 문제 정의 (Problem):

    • 튜닝 리포트 작성은 AI 서버와의 비동기 통신이 필요, 생성 타이밍을 통제하기 어려움
    • 수동 트리거 혹은 사용자 요청 기반의 리포트 생성은 사용자 경험을 저해할 수 있음
    • 매칭 결과에 대한 알림 전달이 누락되거나 타이밍이 어긋나는 경우, 서비스 신뢰도가 저하됨
  • 가설 (Hypothesis):
    Spring Batch를 이용해 매칭 상태가 확정된 사용자들에 한해서 알림이 가기 전날 새벽동안 튜닝 리포트를 생성하고, 사용자에게 알림을 발송 → 사용자 참여율과 리텐션이 높아질 수 있음

목표가 아닌 것 (Non-goals)

  • 실시간 리포트 생성: 본 시스템은 배치 기반이며, 실시간 생성은 별도 서비스로 관리함
  • 개별 리포트 수정 기능: AI가 생성한 리포트는 수정 불가한 콘텐츠로 간주하며, 별도 편집 기능은 고려 대상이 아님

설계 및 기술 문서 (Architecture & Technical Docs)

1. 튜닝 리포트 프로세스 개요

1-1. AI 분석 결과 저장

  • AI 서버가 사용자 간 매칭 데이터를 분석하여 백엔드로 전송
  • 백엔드는 해당 결과를 DB에 저장 (tuning_reportis_visible = false 초기값 저장)

1-2. Spring Batch 실행 트리거 (카테고리 별 해당 요일의 오후 12:30마다)

  • is_visible = false 인 리포트 항목 조회
  • 해당 리포트의 is_visible = true로 상태 변경
  • 사용자에게 알림 전송

2. Spring Batch 설계 구성

구성 요소 이름 설명
Job TuningReportVisibilityUpdateJob 하나의 배치 작업 단위이 Job은 일정 주기로 실행되며, 공개되지 않은 튜닝 리포트를 찾아서 공개 처리함
Step updateVisibilityStep Job 내부에서 실제 작업이 수행되는 단위 단계한 Step은 Reader → Processor → Writer 순서로 구성됨
Reader JpaPagingItemReader<TuningReport> 또는 JdbcPagingItemReader DB에서 아직 공개되지 않은 리포트를 일정 개수씩 조회조회 조건: is_visible = false
Writer ItemWriter<TuningReport> 또는 JpaItemWriter 읽어온 리포트들의 is_visible 값을 true로 변경하고,배치 작업 종료 후 알림 서비스를 호출해 사용자에게 리포트 공개 알림 전송
  • Reader 조건 인덱스 최적화 고려
    • 현재 기획 상 최근 30일간의 튜닝 리포트만 조회하도록 되어 있으나, 생성되는 튜닝 리포트가 많아질 수록 is_visible = false 조건만으로는 성능 저하가 발생할 수 있으므로 조회 조건에 인덱스 추가
CREATE INDEX idx_tuning_report_is_visible ON tuning_report (is_visible);

3. 파일별 역할 (domain 패키지 외 생성)

파일명 역할
TuningReportBatchConfig.java Job, Step, Chunk, Reader/Writer 등록
TuningReportReader.java JpaPagingItemReader or JdbcPagingItemReader 설정 분리
TuningReportWriter.java ItemWriter 구현 → 공개 처리 + 알림 발송
TuningReportJobLauncher.java JobLauncher로 배치 Job 수동 실행 (테스트 등)
TuningReportScheduler.java @Scheduled로 주기적 자동 실행 설정
  • 각 파일에 하나의 역할만 부여하여 단일 책임
  • 추후 다른 형태의 튜닝 리포트 배치를 추가하더라도 Job, Step, Reader, Writer만 쉽게 생성 가능
  • 각 컴포넌트에 대해 개별 단위 테스트 가능
  • 특정 컴포넌트 예외 시 분리 대응이 쉬움

4. Spring Batch 메타 테이블 관리

Spring Batch는 Job 실행 내역 관리를 위해 BATCH_JOB_INSTANCE, BATCH_JOB_EXECUTION, BATCH_STEP_EXECUTION 등의 메타 테이블을 사용함

  • 로컬 테스트에서는 spring.batch.initialize-schema=always로 자동 생성
  • 운영 환경에서는 initialize-schema=never로 설정하고 DDL을 수동 적용할 것
  • 메타 테이블 삭제 시 Job 재실행 이력이 초기화되므로 주의 필요

5. 예외 처리 전략

5-1. faultTolerant() + skip() / retry()

  • faultTolerant() : skip(), retry() 등을 활성화하는 선언
  • skip() : 특정 예외 발생 시 해당 아이템만 건너뛰고 계속 진행
  • retry() : 일시적인 예외는 몇 번 재시도 후 계속 진행
.step("updateVisibilityStep")
    .<TuningReport, TuningReport>chunk(10)
    .reader(reader)
    .writer(writer)
    .faultTolerant()
    .skip(NotificationSendException.class)         // 알림 전송 실패는 그냥 skip
    .skipLimit(10)                                 // 최대 10번까지만 skip 허용
    .retry(OptimisticLockingFailureException.class) // DB 충돌은 재시도
    .retryLimit(3)                                 // 3번까지 retry
  • chunk(10) 의 이유
    • chunk size는 한 번에 처리할 리포트의 개수 (트랜잭션 단위로 묶임)
    • 튜닝 리포트는 시그널 상태에서 매칭 상태로 전환된 사용자 쌍마다 생성되며, 주 1회 알림이 발송되기 때문에 현재 설정된 chunk 크기(10)는 적절한 처리량과 실패 시 복구 범위를 고려한 수준이라고 볼 수 있음

5-2. JobExecutionListener 또는 StepExecutionListener 사용

  • 전체 Job의 성공/실패 여부를 한 번에 볼 수 있음
@Bean
public JobExecutionListener jobLoggerListener() {
    return new JobExecutionListener() {
        @Override
        public void afterJob(JobExecution jobExecution) {
            if (jobExecution.getStatus() == BatchStatus.COMPLETED) {
                log.info("✅ 튜닝 리포트 공개 작업 완료!");
            } else {
                log.error("❌ 튜닝 리포트 공개 작업 실패! 상태: {}", jobExecution.getStatus());
            }
        }
    };
}
  • 예외 종류
    • 알림 전송 실패 : NotificationSendExceptionskip()
    • DB 저장 중 충돌 : OptimisticLockingFailureExceptionretry()로 감싸기
    • Runtime Error : 로그 남기고 skipLimit 이내로 처리 (전체 Job이 멈추지 않도록 함)