요약
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:922 이 BACKTEST_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 재개를 막지 않습니다. 순서는 이렇게 봅니다.
- backend 체크섬 handoff 수정 (
kcrmin)
- root user-data UUID correlation id (
kcrmin)
- root 포인터 + Flyway 번들 → 배포 → 검증 (
kcrmin)
- INT03 1회 재개 허가 (
kcrmin → hjcud)
- 이 이슈 — 언제든, 위와 독립 (
hjcud)
5번이 먼저 병합되면 3번 배포에 함께 실어 드립니다. 아니면 다음 포인터 갱신에 태우겠습니다.
요약
INT03 통제 재현(run
c0df2755-01eb-3660-b57e-be20ab73001a)에서 드러난 두 결함 중 이 저장소 몫을넘깁니다.
먼저 좋은 소식: 이 이슈의 요청은 INT03 을 막고 있지 않습니다. 1차·2차 장애의 직접 원인은 둘 다
kcrmin소유(backend 체크섬 handoff, root user-data)이고 그쪽을 고치는 중입니다. 여기 적는 것은 같은일이 다시 일어났을 때 무한 재시도로 번지지 않게 하는 방어입니다. 급하게 처리하지 않으셔도 됩니다.
관찰된 사실 (읽기 전용)
이 저장소의 코드는 대체로 옳습니다
wiring.py:1485-1498은 이미 정확히 옳은 일을 합니다.JobNotSatisfiable→ terminalFAILED발행 →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)이 사라지고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:922이BACKTEST_WORKER_CORRELATION_ID를 그대로 correlation id 로 씁니다. 배포 환경은그 값에 EC2 instance id 를 넣고 있었고(root 에서 고칩니다), 같은 값이
BACKTEST_WORKER_ID에도 들어갑니다.두 값은 의미가 다릅니다.
BACKTEST_WORKER_ID— 누가 실행했는가. instance id 가 적절하고 UUID 일 필요가 없습니다.metadata.correlationId. UUID 형식이 계약입니다.제안:
production.py가 correlation id 를 읽을 때 UUID 로 파싱해 검증하고, 아니면 기동 시점에ConfigurationError로 실패합니다. 지금은 잘못된 값이 기동을 통과해 첫 실패 발행 시점까지 숨어있었습니다. 기동 실패가 훨씬 낫습니다 —
BACKTEST_LOG_LEVEL을 그렇게 하신 것과 같은 방식입니다.임의 문자열이나 instance id 를 UUID 필드에 넣지 말아 주세요. root 는 인스턴스별로 안정적인 UUID 를
결정론적으로 파생해 공급합니다.
TDD 로 부탁드리는 시험
JobNotSatisfiable이 terminalFAILED/REQUIRED_INPUT_UNAVAILABLE로 발행된다PERMANENT_FAILURE이고 원래 reason_code 가 보존된다HANDLER_ERROR:*재시도로 변하지 않는다 (JobResult.RETRY가 아님)metadata.correlationId가 UUID 계약을 만족한다하지 않은 것
기존 run 을 대체·재실행하지 않았고, 신규 공개 release/backtest run 을 만들지 않았고, DLQ 를
receive/delete/redrive 하지 않았고, worker 를 재시작하지 않았고, ASG 를 조작하지 않았고, DB 를 직접
수정하지 않았습니다. 위 사실은 전부 읽기 전용 조회와 소스 확인에서 나왔습니다.
병합 순서
이 이슈의 수정은 INT03 재개를 막지 않습니다. 순서는 이렇게 봅니다.
kcrmin)kcrmin)kcrmin)kcrmin→hjcud)hjcud)5번이 먼저 병합되면 3번 배포에 함께 실어 드립니다. 아니면 다음 포인터 갱신에 태우겠습니다.