Skip to content

A terminal failure must survive its own publish failing, and correlation ID must be a validated UUID #74

Description

@kcrmin

요약

INT03 통제 재현(run c0df2755-01eb-3660-b57e-be20ab73001a)에서 드러난 두 결함 중 이 저장소 몫
넘깁니다.

먼저 좋은 소식: 이 이슈의 요청은 INT03 을 막고 있지 않습니다. 1차·2차 장애의 직접 원인은 둘 다
kcrmin 소유(backend 체크섬 handoff, root user-data)이고 그쪽을 고치는 중입니다. 여기 적는 것은 같은
일이 다시 일어났을 때 무한 재시도로 번지지 않게 하는 방어
입니다. 급하게 처리하지 않으셔도 됩니다.


관찰된 사실 (읽기 전용)

run         c0df2755-01eb-3660-b57e-be20ab73001a
message     d6c7eed3-bc69-40a2-b753-b2c0a7f52253
worker      i-07a6870a8c4c199dc
발생        2026-08-09 10:17:01 KST
배포 root   70cdd868f9daff5644ddd5646bee7ddc0faf5a67
release     https://github.com/Idea2Strategy/Idea2Strategy/actions/runs/31285790795

1차: JobNotSatisfiable: compiled plan sha256:f98de8f7bde5c44eaadf82acad874d9ba7c10eae0d56030687fa706f49a2e850
     is not resolvable
2차: ContractValidationError: backtest_result_event.metadata.correlationId:
     'i-07a6870a8c4c199dc' does not match UUID pattern
결과: HANDLER_ERROR:ContractValidationError 로 attempt 1~5 재시도, run 은 QUEUED 로 남음

이 저장소의 코드는 대체로 옳습니다

wiring.py:1485-1498 은 이미 정확히 옳은 일을 합니다.

try:
    binding = self.bind(envelope, context)
except JobNotSatisfiable as exc:
    self._publish(envelope, self._correlation_id, status="FAILED", ...,
                  failureCode=exc.reason_code, retryable=False)
    return JobOutcome(JobResult.PERMANENT_FAILURE, reason_code=exc.reason_code)

JobNotSatisfiable → terminal FAILED 발행 → PERMANENT_FAILURE(재시도 없음). 구현되어 있습니다.

막힌 이유는 self._correlation_id 가 UUID 가 아니어서 _publish 가 계약 검증에서 던졌고, 그 예외가
__call__ 밖으로 나가 worker.py:548 에서 HANDLER_ERROR 로 바뀐 것입니다. 즉 주입된 설정이 원인이고
로직이 원인이 아닙니다.

그 설정은 root user-data 가 넣습니다 — kcrmin 이 UUID 로 고칩니다.


요청 1 — terminal 실패 발행이 실패해도 재시도 루프가 되지 않게

지금은 terminal 결과를 발행하지 못하면 그 예외가 handler 밖으로 나가고, outer worker 가 그것을
HANDLER_ERROR:<타입> 으로 바꿔 RETRY 합니다(worker.py:544-548). 그래서

  • 원래 실패 원인(REQUIRED_INPUT_UNAVAILABLE)이 사라지고
  • 재시도해도 결코 성공할 수 없는 메시지가 maxReceiveCount 까지 반복되고
  • run 은 QUEUED 로 남아 무엇이 문제였는지 아무도 알 수 없습니다

이번에는 발행 실패의 원인이 설정이었지만, 발행은 다른 이유로도 실패할 수 있습니다(계약 변경, 일시적
네트워크, 직렬화). terminal 판정이 이미 내려진 뒤의 발행 실패는 판정을 뒤집어서는 안 됩니다.

제안: bind()except JobNotSatisfiable 블록에서 _publish 를 감싸고, 발행이 실패해도
JobResult.PERMANENT_FAILURE원래 exc.reason_code 반환합니다. 발행 실패는 별도로 기록합니다
(로그 + 가능하면 attempt 행의 failure_code).

어느 쪽을 우선할지는 이 저장소의 판단입니다. 다만 원래 실패 코드를 보존하는 것이 핵심입니다 —
HANDLER_ERROR:ContractValidationError 는 진짜 원인을 가립니다.

요청 2 — worker id 와 correlation id 의 의미 분리

production.py:922BACKTEST_WORKER_CORRELATION_ID 를 그대로 correlation id 로 씁니다. 배포 환경은
그 값에 EC2 instance id 를 넣고 있었고(root 에서 고칩니다), 같은 값이 BACKTEST_WORKER_ID 에도 들어갑니다.

두 값은 의미가 다릅니다.

  • BACKTEST_WORKER_ID누가 실행했는가. instance id 가 적절하고 UUID 일 필요가 없습니다.
  • correlation id — 결과 이벤트 계약의 metadata.correlationId. UUID 형식이 계약입니다.

제안: production.py 가 correlation id 를 읽을 때 UUID 로 파싱해 검증하고, 아니면 기동 시점에
ConfigurationError 로 실패합니다. 지금은 잘못된 값이 기동을 통과해 첫 실패 발행 시점까지 숨어
있었습니다. 기동 실패가 훨씬 낫습니다 — BACKTEST_LOG_LEVEL 을 그렇게 하신 것과 같은 방식입니다.

임의 문자열이나 instance id 를 UUID 필드에 넣지 말아 주세요. root 는 인스턴스별로 안정적인 UUID 를
결정론적으로 파생해 공급합니다.

TDD 로 부탁드리는 시험

  • JobNotSatisfiable 이 terminal FAILED / REQUIRED_INPUT_UNAVAILABLE 로 발행된다
  • 그 발행이 실패해도 결과가 PERMANENT_FAILURE 이고 원래 reason_code 가 보존된다
  • 그 경우가 HANDLER_ERROR:* 재시도로 변하지 않는다 (JobResult.RETRY 가 아님)
  • correlation id 가 UUID 가 아니면 기동이 실패한다
  • 결과 이벤트의 metadata.correlationId 가 UUID 계약을 만족한다

하지 않은 것

기존 run 을 대체·재실행하지 않았고, 신규 공개 release/backtest run 을 만들지 않았고, DLQ 를
receive/delete/redrive 하지 않았고, worker 를 재시작하지 않았고, ASG 를 조작하지 않았고, DB 를 직접
수정하지 않았습니다. 위 사실은 전부 읽기 전용 조회와 소스 확인에서 나왔습니다.

병합 순서

이 이슈의 수정은 INT03 재개를 막지 않습니다. 순서는 이렇게 봅니다.

  1. backend 체크섬 handoff 수정 (kcrmin)
  2. root user-data UUID correlation id (kcrmin)
  3. root 포인터 + Flyway 번들 → 배포 → 검증 (kcrmin)
  4. INT03 1회 재개 허가 (kcrminhjcud)
  5. 이 이슈 — 언제든, 위와 독립 (hjcud)

5번이 먼저 병합되면 3번 배포에 함께 실어 드립니다. 아니면 다음 포인터 갱신에 태우겠습니다.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions