운영팀 OPS-005(이벤트 발행 실패) 코드검증 → dcy-031 confirm-discovery. 스코프 B(재시도/강제재연결)+A(idle keepalive)+D(DLQ 가시성/emit 경보). REQ-091~094 draft-req → confirm-req 완료(4건 Approved). M2 트랙B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
14 KiB
14 KiB
0. 문서 상태
이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.
- 상태: confirmed
- 문서 ID: dcy-031_20260619_event-publisher-connection-reliability
- 이슈명: 이벤트 발행 연결 신뢰성 — idle heartbeat 사망 후 첫 발행 유실 + DLQ 가시성 (OPS-005)
- 작성 시각(KST): 2026-06-19 23:00
- 마지막 갱신 시각(KST): 2026-06-19 23:50
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: REQ-091, REQ-092, REQ-093, REQ-094
1. 계획 상태 요약
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.
- 연계 문서 현황:
- 관련 점수 파일: 미생성
- 관련 TODO/메모: ROADMAP.md(M2 트랙B), memory/trex-backlog.md(Task #16)
- 해석:
- 운영팀 OPS-005 리포트(trex-core RabbitMQ 발행 실패)를 코드로 검증한 결과, 발행 전용 연결의 idle heartbeat 사망 후 첫 발행이 재시도·DLQ 없이 유실되는 실결함을 확인했다.
- M2(매도전략 tsl 라이브, 6/28 목표)에서 슬롯 전이 이벤트 유실은 매도 미발동=실손실로 직결되므로, tsl 라이브 선행 조건으로 발행 신뢰성을 보강한다.
2. 요구사항 판정 결과
이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다.
- 판정: 신규
- 근거
- 기존 dcy-006(RabbitMQ transport 도입)·dcy-009(멀티 인스턴스 토폴로지)는 전송 계약 수립이 주제였고, 발행 연결의 idle 수명/재시도 신뢰성은 다루지 않았다.
- OPS-005는 운영 환류로 발견된 신규 신뢰성 결함이며 독립 주기로 추적하는 것이 명확하다.
3. 문제점의 요약
문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다.
- 현재 상태
- 발행자는 단일 공유 인스턴스다(
runtime/daemon.py:206self.event_publisher = _build_transport). 슬롯 전이 이벤트는_make_event_emitter(daemon.py:238-248)가publish_event로 topic exchangetrex.events에 발행한다. - 발행 전용 연결은 publish 사이에
process_data_events를 돌리지 않는다(consumer 루프와 달리 —rabbitmq.py:337/379는 0.2초마다 호출).params.heartbeat = 30(rabbitmq.py:83)이나 idle 구간엔 heartbeat가 나가지 않아 ~60초 후 RabbitMQ 서버가 연결을 종료한다. - 다음 발행 시
_ensure_connected(rabbitmq.py:94-103)가 staleis_open=True를 신뢰해 재연결을 건너뛰고,basic_publish(confirm_delivery 대기,rabbitmq.py:88/180)가 실패한다 →AMQPError(rabbitmq.py:193) →_mark_disconnected+TransportUnavailableError. - 이 예외가
emit()(daemon.py:245)로 올라가:246에서 잡혀 warning 로그만 남기고 삼켜진다("publish failure must not roll back"). 재시도·DLQ 없이 그 이벤트는 유실된다. 다음 발행은 재연결돼 정상 → 증상 = 한산 구간 직후 첫 이벤트 간헐 유실. - 한편 unroutable(구독자 없음)은
mandatory=True→UnroutableError(rabbitmq.py:190) →_publish_dlq로trex.events.dlq(durable)에 보존된다 → 유실 아님. 단 DLQ를 소비/알림하는 주체가 없어 조용히 누적되는 가시성 공백이 있고,_publish_dlq는 best-effort(rabbitmq.py:215실패 시 로그만)다.
- 발행자는 단일 공유 인스턴스다(
- 기대 상태
- 발행 연결의 idle 사망을 예방하고, 사망/half-open 시에도 첫 발행이 투명 재시도로 복구되어 이벤트가 유실되지 않는다.
- DLQ(unroutable + 발행 실패 양쪽)의 유입/깊이가 운영에서 관측·경보 가능하다.
- 이해관계자
- 작성/검수: 팀장(개발 거버넌스)
- 구현: trex-core-dev
- 영향 소비자: trex-orb-v1(라이브), trex-tsl-v1(M2 라이브 예정)
- 운영: TREX 운영팀(DLQ 가시성/경보 소비)
- 제약사항
- trex-core는 M1 운영 배포본(
9e35467)이다. wire 계약·기존 consume/RPC 경로 무회귀가 필수다. - pika BlockingConnection은 스레드 비안전 — keepalive는 발행 전용 연결을 소유한 스레드에서만 I/O를 돌려야 한다.
- trex-core는 M1 운영 배포본(
주요 사용자 시나리오
- 점심·저활동·개장 직후 등 한산한 구간 뒤 슬롯이 HOLDING으로 전이될 때, 첫 전이 이벤트가 유실되지 않고 tsl가 수신해 트레일링을 시작한다.
- RabbitMQ 일시 단절/재시작 후에도 발행이 재연결·재시도로 복구되어 청산 전이 이벤트가 누락되지 않는다.
- 운영자가 DLQ 유입(구독자 없음 또는 발행 실패)을 대시보드/로그/경보로 인지하고 환류한다.
4. 이번에 정의할 변경
이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다.
- (B) 발행 재시도 + 강제 재연결:
publish_event가is_open을 맹신하지 않고, 발행 실패 시 강제 재연결 후 1회 재시도한다. 재시도까지 실패한 경우에만 실패로 처리한다. - (A) idle keepalive: 발행 전용 연결에 heartbeat 주기 내
process_data_events틱을 보장해 서버 측 연결 종료를 예방한다. - (D) DLQ 가시성/경보:
trex.events.dlq유입·깊이를 관측 가능하게 한다(unroutable + 발행 실패 양쪽 추적). 운영 관측성(dcy-029) 자산 재사용 여부 검토.
영향 범위
- 코드 영향:
trex_core/adapters/messaging/rabbitmq.py(publish_event/_ensure_connected/_publish_dlq+ keepalive 추가)trex_core/runtime/daemon.py(_make_event_emitter실패 처리 강화, keepalive 틱 연동)
- 문서 영향:
docs/operations.md(DLQ 가시성/경보 운영 절차),docs/runtime-flows.md(발행 재시도/keepalive 흐름) 갱신 검토
- 운영 영향:
- DLQ 모니터링/경보 신규 절차. 배포는 trex-core 재배포 필요(M1 배포본 갱신) — 사장님 release 결재 대상.
5. 요구사항 목록
기능 요구사항
- FR-1 (B):
publish_event는 연결 종료/half-open으로 발행 실패 시 강제 재연결 후 1회 재시도한다. 재시도까지 실패한 경우에만TransportUnavailableError를 올린다.is_open기반 스킵에 의존하지 않는다. - FR-2 (A): 발행 전용 연결에 idle keepalive를 도입해 heartbeat 주기 내 I/O가 보장되도록 한다(서버 측 idle 종료 예방). pika 스레드 안전성을 위반하지 않는다.
- FR-3 (D):
trex.events.dlq유입/깊이를 운영에서 관측·경보 가능하게 한다(메트릭/로그/소비자 중 택1). unroutable과 발행 실패 양쪽이 추적되어야 한다. - FR-4:
_make_event_emitter의 발행 실패 처리를 보강한다 — 재시도 후에도 실패 시 단순 warning 삼킴이 아니라 식별 가능한 경보 레벨 로그/메트릭을 남긴다.
비기능 요구사항
- NFR-1: M1 운영 배포본(
9e35467) 무회귀 — 기존 consume/RPC/wire 계약과 토폴로지(declare_topology)는 불변. - NFR-2: 재시도/keepalive가 execution-loop를 장시간 블록하지 않는다(매도 타이밍 보호) — 재시도 1회 + 명시적 타임아웃 상한.
- NFR-3: 변경은 transport 어댑터·발행 경로에 국소화하고, 슬롯 도메인/broker 집행 로직은 건드리지 않는다.
범위 경계
- In Scope
- 발행 전용 연결의 재시도·재연결·keepalive(B+A)
- DLQ 유입/깊이 가시성·경보(D)
- emit 실패 처리 강화(FR-4)
- Out of Scope
- 이벤트 영속 outbox(옵션 C) — 본 주기 제외, 필요 시 후속(M3) Discovery. (사장님 스코프 결정 B+A+D)
- consumer(orb/tsl) 측 idempotency 구현 — 본 주기는 검증/확인까지, 구현은 해당 repo 별도 추적
- RPC client(
call_rpc) 연결 정책 — 본 주기는 이벤트 발행 경로 한정
6. 리스크/가정 목록
리스크
- R-1 (High): trex-core는 M1 라이브 배포본이고 발행 경로는 orb(라이브)·tsl(M2)에 동시 영향 → 회귀 시 사고 반경 큼.
- 완화 방안: opus QC 강제, 기존 회귀 스위트 + wire 계약 무회귀 검증, transport 어댑터로 변경 국소화.
- R-2 (Medium): keepalive가 pika 스레드 비안전성과 충돌 가능(다른 스레드에서 I/O 호출 시 손상).
- 완화 방안: 발행 전용 연결을 소유한 스레드에서만 keepalive I/O 실행하도록 설계 단계에서 확정(OQ-1).
- R-3 (Medium): 재시도는 at-least-once 의미 → 동일 이벤트 중복 발행 가능.
- 완화 방안: 소비자(orb/tsl) idempotency 검증(OQ-2). 슬롯 전이 이벤트는 멱등 적용 가능 추정.
가정
- A-1: 본 변경은 transport 어댑터·발행 경로에 한정되며 trex-core dcy-028(strategy-state, BAT-042 released) 등 기존 계약은 불변이다.
- A-2: 슬롯 전이 이벤트 소비자(orb/tsl)는 중복 수신에 견고하거나 견고화 가능하다.
- A-3: DLQ 가시성은 dcy-029(운영 관측성) 로깅/뷰 자산을 일부 재사용할 수 있다.
오픈 질문
- OQ-1: keepalive를 별도 스레드로 둘지, execution-loop 틱에 발행 연결
process_data_events를 삽입할지 (pika 스레드 안전성 제약).- 상태: Deferred
- 처리 방안: draft-batch-design에서 trex-core-dev가 스레드 소유 모델 기준으로 결정. REQ에 설계 결정 추적 인계.
- 종료 조건: batch design 문서에 keepalive 실행 위치·스레드 소유가 확정되면 Answered 처리.
- OQ-2: 재시도 at-least-once로 인한 이벤트 중복 시 소비자(orb/tsl)가 멱등한가.
- 상태: Deferred
- 처리 방안: delivery 단계에서 trex-orb-v1/trex-tsl-v1 이벤트 핸들러의 멱등성 확인(§10 DATA로 추적). 비멱등이면 해당 repo 후속 티켓. confirm-discovery를 막지 않음(전이 이벤트는 멱등 적용 가능 추정).
- 종료 조건: 두 소비자의 중복 수신 동작이 확인되면 Answered.
- OQ-3: DLQ 가시성을 메트릭/로그/소비자 데몬 중 무엇으로 구현할지.
- 상태: Deferred
- 처리 방안: draft-batch-design에서 dcy-029 관측성 자산 재사용 가능성과 함께 결정.
- 종료 조건: batch design에 DLQ 가시성 구현 방식이 확정되면 Answered.
7. 초기 성공 기준
- S-1: idle(>heartbeat×2) 구간 직후 발행이 유실 없이 성공한다(재연결·재시도로 복구).
- S-2: RabbitMQ 강제 단절/재시작 시나리오에서 발행 이벤트 누락 0건.
- S-3: DLQ 유입(unroutable + 발행 실패)이 관측 지표로 노출되고 경보 가능하다.
측정 방식 및 데이터 출처
- S-1 측정 방식: idle 주입 후 발행 통합 테스트(연결 강제 종료 후 publish) / 데이터 출처: trex-core 테스트 스위트(신규 케이스).
- S-2 측정 방식: broker 재시작 fault-injection 테스트에서 발행 카운트 vs 수신 카운트 대조 / 데이터 출처: 통합 테스트 + trex-sim e2e(M2 트랙C 연계).
- S-3 측정 방식: DLQ 깊이/유입 메트릭 노출 확인 / 데이터 출처: 관측 지표(로그/메트릭),
docs/operations.md.
8. REQ로 넘기기 전 확인 체크
- 범위(In Scope/Out of Scope) 확정
- 기준: 사장님 스코프 B+A+D 확정 반영 + outbox(C) 제외 합의 명시 (2026-06-19 확정)
- 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1~3의 테스트/메트릭 출처가 trex-core 스위트·trex-sim e2e로 연결됨
- High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: R-1(회귀) 해소 책임 = trex-core-dev 구현 + opus qc-reviewer (CONFIRM① 확정)
- 모든 오픈 질문(OQ)
Open상태 없음 확인- 기준: OQ-1/3 Deferred(design 인계), OQ-2 Deferred(delivery 검증) 처리 완료 — Open 0개
- Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: 사장님(CEO) via 팀장
9. 파일 처리 결과
- 처리 결과: 생성
- 생성 경로: docs/discovery/dcy-031_20260619_event-publisher-connection-reliability.md
- 참조 문서:
- trex_core/adapters/messaging/rabbitmq.py
- trex_core/runtime/daemon.py
- docs/discovery/dcy-006_20260605_strategy-module-rabbitmq-transport.md
10. 사용자 결정 필요 항목 요약
DECIDE
- (해소됨) 수정 스코프 = B(재시도)+A(keepalive)+D(DLQ 가시성). outbox(C)는 제외→M3 후보. — 사장님 결정 2026-06-19, §5에 반영.
CONFIRM
- (확정) opus QC: 본 batch는 고위험(M1 라이브 trex-core)이라 qc-reviewer를 opus로 승격 운용. — 사장님 확정 2026-06-19.
- (확정) M2 게이트: tsl(M2) 라이브 전 선행 완료 — 6/28 릴리즈 창 내 배포·검증. — 사장님 확정 2026-06-19.
DATA
- {{DATA_CONSUMER_IDEMPOTENCY}}: orb/tsl 이벤트 핸들러의 중복 수신 멱등성 확인 결과(OQ-2 종료용).
11. Discovery Freeze
REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다.
섹션 참조
- 범위: # 5. 요구사항 목록 > 범위 경계
- 리스크/오픈 질문: # 6. 리스크/가정 목록
- 성공 기준: # 7. 초기 성공 기준
- 인계 전 확인: # 8. REQ로 넘기기 전 확인 체크
- 사용자 결정 필요: # 10. 사용자 결정 필요 항목 요약
Freeze 플래그
- Handoff Decision: REQ Drafting 진행 가능
- Handoff Rationale: confirm-discovery 완료. 스코프 B+A+D 확정, CONFIRM 2건(opus QC·M2 게이트) 확정, OQ 전부 Deferred 처리(Open 0). DATA 1건(소비자 멱등성)은 delivery 검증으로 추적. trex-core-dev에 draft-req 인계.
- Ready for REQ Drafting: true
- Confirmed By: 사장님(CEO) via 팀장
- Confirmed At (KST): 2026-06-19 23:20