콘텐츠로 이동

ADR-0030: raw 페이즈는 부분 실패를 격리한다 — 전량 성공 게이트 제거

  • 상태: 승인됨
  • 날짜: 2026-07-20

맥락

ADR-0028은 파이프라인을 4페이즈(raw → 정제 → feature → 분석)로 통합하며 "각 페이즈는 전량 성공 게이트로 순서를 보장한다"고 정했다. 이 ADR 은 그중 raw 페이즈의 게이트만 갱신한다 — 0028 의 나머지 결정(페이즈 구성·feature/분석 경계·분석이 읽는 것)은 그대로다.

raw 페이즈는 벤더가 다른 10개 수집 잡을 병렬로 돌린다. 전량 성공 게이트는 이들을 하나의 성공/실패로 묶는데, 실제로는 서로 독립이다. BigKinds 뉴스 수집이 실패해도 KIS 가 받아온 가격 raw 는 멀쩡하다. 그런데 게이트가 And 라 소스 하나가 죽으면 무관한 소스의 정제·분석까지 통째로 멈춘다. 재무 수집 (ingest-raw-financial)은 canonical 스텝조차 없어 아무 정제도 공급하지 않는데도 전체를 막았다.

스케줄러가 DISABLED 라 아직 체감되지 않았을 뿐, ENABLED 로 바꾸면 벤더 한 곳의 간헐 장애가 그날 파이프라인 전체를 지우는 일이 상시화된다(ALPHA-386 실측: 5회 시도 중 4회가 환경성 실패였다).

결정

  • raw 페이즈에 전량 성공 게이트를 두지 않는다. 일부 소스가 실패해도 정제·feature·분석은 계속 돈다.
  • 근거는 정제 스텝의 성질이다: 정제는 빈 입력을 정상 성공으로 처리한다(raw 키가 0개면 루프가 돌지 않고 exit 0). 따라서 "어느 정제 잡이 어느 raw 를 필요로 하는가"라는 의존 맵을 상태머신에 명시할 필요가 없다 — 각 스텝이 있는 만큼 처리하면 결과가 같다.
  • 실패 신호는 두 갈래로 보존한다: ① raw 페이즈 직후 SNS 알림을 즉시 발행하고 계속 진행한다 ② 실행 마지막에 raw 결과를 다시 판정해 부분 실패였으면 런을 FAILED 로 마감한다(알림 재발행 없음).
  • 고립 심볼 실패는 임계로 예외를 둘 수 있다(ALPHA-798): KIS 가격·EOD 수급·장중 수급은 --max-failed-symbols 1을 명시해 실패 심볼 1개까지 실행만 성공으로 마감한다. 수집 로그의 status=partial·실패 상세·failed_records는 그대로 남아 원장 데이터 상태는 INCOMPLETE다. 2개 이상, 저장 0건, 소스 전체 중단과 가격 신규편입 스캔 미완료는 기존처럼 비영 종료한다.
  • 분석도 부분 입력으로 돈다. 입력 완전성을 실행 전제로 요구하지 않는다.

대안

대안 1 — 정제 잡별로 필요한 raw 소스만 검사한다. 정확해 보이지만 상태머신에 데이터셋 의존 그래프가 들어온다. 정제가 빈 입력을 이미 정상 처리하므로 이 그래프는 동작을 바꾸지 않으면서 유지 대상만 늘린다(Rule 2). 게다가 "어느 소스가 어느 산출물에 필수인가"는 로직 소유자의 판단 영역이라, 인프라 정의에 박아두면 결정이 잘못된 자리에 고인다.

대안 2 — 분석 앞에만 전량 성공 게이트를 남긴다. 수집·정제의 독립은 얻으면서 분석 산출물의 오염은 막는다. 기각한 이유: 준실시간에서는 "완전한 입력"이라는 상태가 존재하지 않는다. 데이터가 계속 들어오고 분석은 항상 그 시점까지의 것으로 돈다. 입력 완전성 게이트는 수집 주기가 짧아질수록 "매번 불성립"으로 수렴해, 결국 지금 겪는 문제를 분석 페이즈로 옮기는 것에 지나지 않는다.

대안 3 — 게이트를 지우고 끝낸다(마감 판정 없이). 부분 성공 런이 Succeed 로 끝난다. 정제가 run 스코프라(ALPHA-389) 실패 런의 raw 는 다음 런이 자동으로 주워가지 않으므로, 알림과 실패 상태가 모두 없으면 승격되지 않은 raw 를 아무도 모른다(Rule 12 위반).

결과

  • 벤더 한 곳의 장애가 그날 파이프라인 전체를 지우지 않는다. 스케줄러 ENABLED 의 선결 조건 하나가 해소된다.
  • ALPHA-387(KRX 기준일)이 컷오버 하드 블로커에서 내려온다 — KRX 실패는 이제 그날 ETF canonical 결손으로 격리된다. 다만 trdDd 백필 수단이 없어 실패한 날의 스냅샷이 영구 결손인 것은 그대로라, 티켓 자체는 유효하다.
  • 알림은 raw 직후에 쏴야 한다(마감 판정에 얹으면 안 된다). 뒤 페이즈에 LLM 호출(tag-news·분석)이 있어 소요시간 상한이 없고, 최상위 TimeoutSeconds 로 실행이 죽으면 어떤 Catch 도 타지 않아 알림이 아예 나가지 않는다. 실행 단위 CloudWatch 알람은 run_id 를 담지 못해 대체재가 못 된다.
  • 분석이 부분 입력으로 도는 대가: 가격 수집이 실패하면 트리거가 결측되고, 분석은 그것을 "데이터가 없었다"가 아니라 "움직임이 없었다"(평온)로 끝낸다. 이 구분은 게이트가 아니라 분석 산출물이 두 상태를 표현해야 풀리며, coverage 계측(ALPHA-452)·게이트 정책(ALPHA-453)이 소유한다.
  • 임계 밖 부분 실패 런은 알림 1통 + 실행 상태 FAILED 로 드러난다. 임계 안 고립 실패는 실행은 성공하지만 원장 INCOMPLETE로 드러난다. 재승격은 여전히 수동 (normalize-<step> --input-run-id <실패한 run_id>)이며, 그 트리거가 이 알림이므로 pipeline_alarm_email 구독이 반드시 살아 있어야 한다.