발행 전용 연결의 idle heartbeat 사망 후 첫 발행 유실 결함 제거. - REQ-091 발행 재시도 + 강제 재연결(_force_reconnect, is_open 불신) - REQ-092 idle keepalive(execution-loop 단일스레드 틱, process_data_events time_limit=0) - REQ-093 DLQ 가시성 + emit 실패 ERROR 경보(구조화 로그, dcy-029 재사용) - REQ-094 baseline 문서 정합(runtime-flows/operations/project-structure) 독립 opus QC PASS-WITH-NOTES → 시정 2건(ruff F401, AC-3 프로덕션 경로) 완료. 554 passed/47 skipped/회귀 0/ruff clean. NFR-1 wire 무회귀(interface-contract diff 0). BAT-047 release-candidate, REQ-091~094 Implemented. dcy-031. M2 트랙B. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
8.4 KiB
BAT-047 Design
- Status: confirmed
- Batch: BAT-047
Architecture Summary
발행 신뢰성 보강은 adapters/messaging/rabbitmq.py의 발행 경로(publish_event, _publish_dlq)와 runtime/daemon.py의 _make_event_emitter에 국소화한다. wire 계약(AMQP 토폴로지/exchange/routing key/payload 스키마)·consume/RPC 경로(consume_rpc_forever, consume_events_forever, call_rpc)·declare_topology·슬롯 도메인·broker 집행 로직은 불변이다.
변경 계층:
adapters/messaging/rabbitmq.py(transport 어댑터 레이어)publish_event: 발행 실패 시 강제 재연결(close+_connect) 후 1회 재시도. 타임아웃socket_timeout으로 상한 제어.is_open맹신 제거.keepalive_tick():process_data_events(time_limit=0)호출 — 발행 전용 연결 소유 스레드에서만 호출._publish_dlq: best-effort 실패 시 WARNING → ERROR 격상.
runtime/daemon.py(runtime 레이어)_make_event_emitter:TransportUnavailableError처리 WARNING → ERROR 격상, routing key + correlation 정보 포함._execution_loop: 각 틱 직전event_publisher.keepalive_tick()호출(동일 스레드 보장).
Changed Areas
- Modules:
trex_core/adapters/messaging/rabbitmq.py—publish_event,keepalive_tick(신규),_publish_dlqtrex_core/runtime/daemon.py—_make_event_emitter,_execution_loop
- Tests:
tests/adapters/messaging/test_publish_reliability.py(신규) — AC 커버tests/application/test_messaging_event_publisher.py— 기존 회귀 유지
- Docs:
docs/runtime-flows.md— 발행 재시도/keepalive 흐름, DLQ 관측 경로 추가(REQ-094)docs/project-structure.md—keepalive_tick컴포넌트 기술 추가(REQ-094)docs/operations.md— DLQ 가시성/경보 운영 절차 신규(REQ-094)
- Interfaces: AMQP wire 계약 변경 없음.
interface-contract.md불변.
Key Decisions
OQ-1: keepalive 실행 위치 — Answered
결정: execution-loop 틱에 event_publisher.keepalive_tick() 삽입(동일 스레드 모델).
근거:
- pika
BlockingConnection은 생성 스레드에서만 안전하게 I/O 호출 가능(공식 문서: "pika is not threadsafe"). event_publisher는 daemon 초기화 시 생성되어_execution_loop스레드(trex-execution)가 독점 소유.- 별도 keepalive 스레드를 띄우면 그 스레드가 발행 전용 연결에
process_data_events를 호출 → 스레드 경계를 넘어 연결에 접근하는 것 → pika 스레드 비안전성 위반. - execution-loop 틱은
self.stop.wait(interval)주기로 이미 동기 루프 구조 → 각 틱 시작 시keepalive_tick()을 먼저 호출하면trex-execution스레드 컨텍스트에서만 실행됨. - keepalive 틱은
process_data_events(time_limit=0)호출 — pika 문서에서 heartbeat frame 처리를 위한 권장 패턴.time_limit=0은 blocking 없이 socket에서 pending frame만 처리. execution_interval_sec(기본 1.0초) <heartbeat=30초의 절반(15초) → 30초 이내 I/O 보장, 서버 측 idle 종료 예방 성립.
별도 스레드 방식 기각 이유: 공유 연결에 두 스레드가 동시 접근 가능 → 발행과 keepalive가 겹치면 연결 손상 또는 예외 발생. 뮤텍스로 보호해도 pika 내부 소켓 상태가 스레드 경계를 넘어 교란될 수 있어 권고되지 않음.
OQ-3: DLQ 가시성 구현 방식 — Answered
결정: 구조화 로그 기반 (dcy-029 관측성 자산 재사용).
근거:
- dcy-029(BAT-045/046)에서 구조화 로깅 인프라(
logging_setup.py,_log.error(), correlation id, json 포맷, Loki 적재)가 이미 구축·검증됨. - 추가 인프라 의존성(Prometheus, StatsD, 소비자 데몬)은 본 주기 범위 밖(M3 후보).
- 소비자 데몬 방식은 별도 상시 프로세스·새 큐·바인딩이 필요해 wire 계약 추가 가능성이 있어 교차 영향 사안(trex-orb-v1·trex-tsl-v1) 검토 필요 → 현 주기 제외.
- 로그 기반:
_publish_dlq호출 시_log.error("DLQ 유입: reason=%s rk=%s", reason, routing_key), DLQ 재발행 실패 시_log.error("DLQ publish failed", exc_info=True),_make_event_emitter재시도 최종 실패 시_log.error("event publish failed (rk=%s)", rk, exc_info=True). - 운영자는 Loki 쿼리
{level="error"} |= "DLQ"또는|= "event publish failed"로 즉시 식별 가능. - DLQ 깊이(queue depth)는 RabbitMQ 관리 UI(
http://broker:15672) 또는rabbitmqctl list_queues name messages로 조회 — 별도 구현 불요.
소비자 데몬 기각 이유: 별도 프로세스 관리 복잡성 + 새 큐/바인딩 = wire 계약 파급 가능성. M3 검토로 이월.
NFR-2: 재시도/keepalive 타임아웃 상한 — Answered
결정:
- 단일 publish 경로 전체 블록 상한: ≤ 5초 (재연결 1회 + 재시도 1회 합산).
_connect()내pika.BlockingConnection(params):params.socket_timeout = 2.0(기존self._timeout = 5.0보다 강화 — 재연결용).params.blocked_connection_timeout = 2.0.basic_publish+confirm_delivery대기: publisher confirms는 channel-level이므로 별도 타임아웃 없음(socket_timeout으로 상한됨).- 강제 재연결
close()+_connect(2.0s)+basic_publish≤ 5초 내 완료 설계.
- keepalive 틱:
process_data_events(time_limit=0)— non-blocking, pending socket frame만 처리.- 실질 지연: 수 밀리초 이내, execution-loop 지연 없음.
- 재시도 횟수: 1회 — at-least-once 의미를 최소화하고 중복 위험도 최소화.
근거: execution_interval_sec 기본 1.0초인데 발행 경로가 5초 이상 블록되면 매도 타이밍 손상 가능. 2초 socket_timeout + 재연결 overhead ≤ 5초가 실전 허용 범위. keepalive는 non-blocking으로 루프 지연 없음.
Edge Cases
- 재시도 중 중복 발행: at-least-once → orb/tsl 소비자 중복 수신 가능. 전이 이벤트는
sequence_atlast-wins 적용 가능(멱등 가능 추정, OQ-2 delivery 검증 추적). - 재시도에서도 연결 실패:
TransportUnavailableError를_make_event_emitter까지 전파 → ERROR 레벨 로그. 슬롯 전이 롤백 없음(NFR-3 — publish failure must not roll back). - keepalive 호출 중 발행 겹침: execution-loop 단일 스레드이므로 실제 동시성 없음.
_execution_loop에서keepalive_tick()→run_once()순서적 호출. MessagingTransport(Protocol) 인터페이스 변경:keepalive_tick()메서드는RabbitMQTransport전용이므로 포트 인터페이스(MessagingTransport)에 추가하지 않는다.daemon.py에서isinstance체크 또는hasattr로 호출하거나,InMemoryTransport에도 no-op 구현을 추가한다. 포트 인터페이스 확장은 wire 계약이 아니라 transport 구현 내부이므로 교차 영향 없음.- 결정:
MessagingTransport포트에keepalive_tick() -> None(default no-op) 추가. 기존 구현(InMemoryTransport)에 no-op 추가. daemon에서self.event_publisher.keepalive_tick()직접 호출. 포트 인터페이스 추가는 transport 어댑터 내부이므로 wire 계약과 무관.
- 결정:
_publish_dlq내 keepalive: DLQ 발행은 같은 channel에서_ensure_connected()후 수행. keepalive와 별도 경로이므로 간섭 없음. DLQ 발행은 no publisher-confirms이므로 block 없음.close()후keepalive_tick()호출:_conn is None체크로 no-op. 데몬 shutdown 시 stop 이벤트 세팅 후 루프 종료 → keepalive 틱 호출 없음.
Architecture Impact
- runtime-flows (발행 재시도/keepalive/DLQ 관측 경로 추가)
- project-structure (
keepalive_tick메서드,MessagingTransport포트 확장 기술)
Reference Doc Update Plan (REQ-094)
docs/runtime-flows.md:슬롯 이벤트 발행/구독흐름: 재시도/강제 재연결 경로, idle keepalive 동작, DLQ 유입 관측 경로 추가.데몬 lifecycle: keepalive 틱이 execution-loop 틱에 삽입됨(OQ-1 결정 반영).
docs/project-structure.md:messaging.py(MessagingTransport 포트) 설명에keepalive_tick()추가.rabbitmq.py설명에 발행 재시도·keepalive 기술 추가.
docs/operations.md:- 신규 섹션 "DLQ 가시성/경보":
trex.events.dlq유입 로그 확인 방법, RabbitMQ 관리 UI queue depth 조회, 운영자 조치 절차.
- 신규 섹션 "DLQ 가시성/경보":