# 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:206` `self.event_publisher = _build_transport`). 슬롯 전이 이벤트는 `_make_event_emitter`(`daemon.py:238-248`)가 `publish_event`로 topic exchange `trex.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`)가 stale `is_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를 돌려야 한다. ## 주요 사용자 시나리오 - 점심·저활동·개장 직후 등 한산한 구간 뒤 슬롯이 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로 넘기기 전 확인 체크 - [x] 범위(In Scope/Out of Scope) 확정 - 기준: 사장님 스코프 B+A+D 확정 반영 + outbox(C) 제외 합의 명시 (2026-06-19 확정) - [x] 성공 지표의 측정 방식/데이터 출처 확정 - 기준: S-1~3의 테스트/메트릭 출처가 trex-core 스위트·trex-sim e2e로 연결됨 - [x] High 영향 리스크 우선순위 및 해소 책임자 지정 - 기준: R-1(회귀) 해소 책임 = trex-core-dev 구현 + opus qc-reviewer (CONFIRM① 확정) - [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - 기준: OQ-1/3 Deferred(design 인계), OQ-2 Deferred(delivery 검증) 처리 완료 — Open 0개 - [x] 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. 요구사항 목록 > 범위 경계](#5-요구사항-목록) - 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) - 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) - 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) - 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#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