본문으로 건너뛰기

분석 디스패치는 커밋 뒤에 하고 실패해도 예외를 올리지 않는다

맥락

잡 row를 만드는 트랜잭션과 heymoa-ai를 부르는 HTTP 호출이 한 흐름에 있다. 호출을 트랜잭션 안에 두면 커밋 전에 AI가 콜백을 보낼 수 있다. 그렇다고 호출 실패 시 예외를 올리면 잡 row까지 롤백된다. 재시도의 근거가 될 기록 자체가 사라진다.

결정

  • 디스패치는 트랜잭션 커밋 후에 한다.
  • 디스패치가 실패해도 예외를 전파하지 않고 잡을 PENDING으로 남긴다.
  • 워치독이 정체된 잡을 재디스패치하되 재시도는 1회로 제한한다. 재차 정체되면 FAILED(WATCHDOG_TIMEOUT)으로 마감한다.
  • AI 호출 타임아웃은 기존 read-timeout 10s가 이미 바운드하므로 별도 타임아웃은 넣지 않는다.

대안

대안 1 — 트랜잭션 안에서 호출. 코드 흐름이 하나로 단순하다. 기각한 이유: 커밋 전에 콜백이 도착하는 경합이 생긴다.

대안 2 — 실패 시 예외 전파. 실패가 즉시 드러나고 호출자가 알 수 있다. 기각한 이유: 잡 row가 롤백돼 통째로 유실되면서 재시도 기회까지 없어진다. 기록이 남아 있으면 나중에 주울 수 있지만 롤백되면 아무것도 못 한다.

대안 3 — 무제한 재시도. 일시적 장애를 끝까지 견딘다. 기각한 이유: 무한 루프를 만든다.

결과

  • 디스패치가 실패해도 잡 기록이 남아 워치독이 회수한다. 커밋 전 콜백 도착 경합도 없어졌다.
  • at-least-once가 됐다. 중복 실행의 안전은 AI의 멱등성에 기댄다. ADR-0008이 그 멱등성을 테이블 모양으로 고정한 카드다.
  • AnalysisDispatchHandler는 실패를 WARN만 남긴다. 그래서 "실패율 100%가 무기한 조용히 유지되는" 경로가 함께 생겼고, 이 경로는 알람 이슈(APP-221)로 분리됐다.
  • 재시도 상한이 1회다. 일시적 장애가 그 창을 넘기면 잡은 복구되지 않고 FAILED로 끝난다.