예외 종류별 처리 표준은 벌크·배치 경로에서 예외를 번역하지 않기로 하면서, 대신 로그 자리를 이렇게 정했다.

벌크 경로의 로그를 StepExecutionListener.afterStep에서 getFailureExceptions로 남기면
실패가 한 곳에서 한 번 남는다.

이 문장이 거짓이면 실패가 한 줄도 안 남는다. 번역하지 않기로 한 대가가 그대로 손실이 된다.


주장 — skip된 예외는 getFailureExceptions에 들어온다

Spring Batch에서 skip된 예외는
StepExecutionListener.afterStep의 StepExecution.getFailureExceptions()에 들어온다.

근거 종류는 1(기술 사실)이다.


반증 조건 — skip이 났는데 목록이 비어 있으면 반증이다

  • refutedgetSkipCount가 1 이상인데 getFailureExceptions가 비어 있다
  • supportedgetFailureExceptions에 그 예외가 들어 있다
  • inconclusive — 잡이 돌지 않거나, getSkipCount가 0이다(skip 설정이 안 걸린 것이다)

getSkipCount를 먼저 보는 이유는 skip이 실제로 일어났는지부터 확인해야 「목록이 비었다」가 의미를 갖기 때문이다.


설계 — 아이템 다섯 중 하나를 skip시키고 afterStep에서 전부 읽는다

아이템 5개를 읽는 chunk 스텝을 만들고, Processor가 3번째에서 IllegalStateException을 던지게 했다. faultTolerant().skip(IllegalStateException.class).skipLimit(10)이다.

afterStep에서 getSkipCount·getProcessSkipCount·getFailureExceptions·스텝 상태·쓰기 건수를 모두 찍는다. SkipListener.onSkipInProcess도 함께 세어, skip이 진짜 일어났는지를 두 경로로 확인한다.

JobRepository가 DB를 쓰므로 이 컴퓨터의 로컬 PostgreSQL 17에 붙었다. 실험 전용 데이터베이스를 새로 만들고 끝나면 지웠다. 환경은 Spring Boot 3.5.16 하나다 — Boot 4에서 Spring Batch API가 어떻게 바뀌었는지 확인하지 않아 범위를 좁혔다.


결과 — skip은 1건, 목록은 0건이었다

PROBE boot=3.5.16 db=proof_batch_skip_failure_log_1789514243_9705
      skip=1 procskip=1 skiplistener=1:IllegalStateException
      fails=0 failclasses=none
      stepstatus=COMPLETED stepexit=COMPLETED written=4 jobstatus=COMPLETED jobfails=0

판정은 refuted다. skip은 세 경로(getSkipCount·getProcessSkipCount·SkipListener)에서 모두 1로 확인됐는데 getFailureExceptions는 비어 있다.


예상과 달랐던 점 — 스텝도 잡도 COMPLETED로 끝난다

목록이 비었다는 것보다 더 눈에 띈 것은 상태다. 스텝과 잡이 모두 COMPLETED이고, 아이템 5개 중 4개만 쓰였다. 실패가 있었다는 흔적이 상태에도 없다.

getFailureExceptions는 이름 그대로 스텝을 실패시킨 예외를 담는 자리다. skip은 「실패시키지 않기로 한 예외」라 거기 들어갈 이유가 없다. 표준 글은 이 둘을 같은 것으로 보고 로그 자리를 정했다.

그래서 표준대로 짜면 이렇게 된다.

  표준 글이 기대한 것 실제
skip된 예외 afterStep에서 한 번 남는다 한 줄도 안 남는다
스텝 상태 COMPLETED
쓰기 건수 5개 중 4개

skip 예외를 잡으려면 SkipListener가 필요하다는 것이 실험에서 보인다. 이번 실험이 그 경로로 1건을 셌기 때문이다. 다만 이 실험은 「SkipListener로 옮기면 충분한가」를 재지 않았다.


이 결과가 표준에 남기는 것 — 로그 자리를 다시 정해야 한다

규칙(벌크 경로는 번역하지 않는다)은 이 실험이 판정하지 않는다. 무너진 것은 그 대가를 받아준다던 로그 자리다. 지금 글대로 하면 skip된 실패가 조용히 사라지고, 스텝은 성공으로 기록된다.

규칙은 바로 고치지 않는다. 표준 글에 > 반증: 줄을 달고 kind: 모순 이슈에서 로그 자리를 SkipListener로 옮길지 정한다.


정리

  • skip 1건, getFailureExceptions 0건. 세 경로로 skip을 확인했으므로 설정 문제가 아니다
  • 스텝과 잡이 COMPLETED로 끝나고 쓰기는 4건이었다. 상태에도 흔적이 없다
  • 표준이 정한 로그 자리로는 skip된 실패가 남지 않는다
  • 재지 않은 것 — Boot 4의 Spring Batch, SkipListener로 옮겼을 때 충분한지, retry가 섞였을 때

자신만의 철학을 만들어가는 중입니다.
최상단으로 이동했습니다!
확대 이미지

댓글남기기