feat(messaging): OPS-005 이벤트 발행 연결 신뢰성 (BAT-047)

발행 전용 연결의 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
This commit is contained in:
2026-06-19 22:39:43 +09:00
co-authored by Claude Opus 4.8
parent bc79f60274
commit d182464b7a
19 changed files with 1067 additions and 43 deletions
+58 -1
View File
@@ -94,7 +94,62 @@ grep "event=order_reject" /tmp/trex-core.log # 거부 — ERROR 레벨
---
## 4. 디버깅 / 트러블슈팅
## 4. DLQ 가시성 / 이벤트 발행 경보 (REQ-093 / dcy-031 / BAT-047)
trex-core는 `trex.events.dlq`(durable) 큐와 구조화 로그를 통해 이벤트 발행 실패를 운영자에게 노출한다.
### DLQ 유입 원인
| 원인 | 경로 | 로그 |
|---|---|---|
| 구독자 없는 이벤트(unroutable) | `UnroutableError` → `_publish_dlq(reason="unroutable")` | ERROR `DLQ entry: reason=unroutable rk=...` |
| DLQ 재발행 자체 실패 | `_publish_dlq` 내 exception | ERROR `DLQ publish failed (rk=...)` |
| 발행 최종 실패(재연결+재시도 후) | `_make_event_emitter` → `TransportUnavailableError` | ERROR `event publish failed (rk=... slot_id=... strategy=...)` |
### 로그 기반 경보 (Loki / grep)
```bash
# DLQ 유입 확인
grep '"level":"error"' /tmp/trex-core.log | grep "DLQ"
# 또는 text 포맷
grep "ERROR.*DLQ" /tmp/trex-core.log
# 발행 최종 실패 확인
grep "event publish failed" /tmp/trex-core.log
```
Loki LogQL:
```logql
{job="trex-core"} | json | level="error" | line_contains "DLQ"
{job="trex-core"} | json | level="error" | line_contains "event publish failed"
```
### RabbitMQ queue depth 확인
```bash
# rabbitmqctl로 DLQ 깊이 조회
rabbitmqctl list_queues name messages | grep dlq
# RabbitMQ 관리 UI: http://<broker>:15672 → Queues → trex.events.dlq
```
### 운영자 조치 절차
1. **DLQ 유입 경보 수신**: 로그 `ERROR ... DLQ entry` 발견.
2. **원인 분류**:
- `reason=unroutable`: 해당 strategy_id의 전략 데몬(orb/tsl)이 꺼져 있거나 큐 바인딩이 없는 경우. 전략 데몬 기동 상태 확인.
- `DLQ publish failed`: RabbitMQ 자체 연결 문제. broker 상태 확인.
- `event publish failed`: 발행 연결 재연결+재시도 후에도 실패. broker 재시작 또는 네트워크 장애. trex-core 재기동 검토.
3. **DLQ 내용 소비**(선택): 적체된 메시지는 수동 drain 또는 strategy 재기동 후 catch-up.
4. **임계값 기준**: DLQ depth > 0 = 경보 대상. depth > 100 = 즉시 조치 권고.
### idle heartbeat 예방 (REQ-092)
trex-core는 발행 연결의 idle 종료를 예방하기 위해 `trex-execution` 루프 각 틱(기본 1초)마다 `event_publisher.keepalive_tick()`(`process_data_events(time_limit=0)`)을 호출한다. `heartbeat=30s` 설정 대비 충분한 I/O 주기를 보장한다.
---
## 5. 디버깅 / 트러블슈팅
| 증상 | 원인 | 조치 |
|---|---|---|
@@ -118,6 +173,8 @@ psql "$TREX_POSTGRESQL_DSN" -c "SELECT slot_id, from_state, to_state, reason, ac
---
---
## 관련 문서
- `docs/interface-contract.md` — RabbitMQ 토폴로지 + 메시지 payload + reason/actor 어휘
- `docs/runtime-flows.md` — 흐름 다이어그램(스냅샷/intent RPC/이벤트/집행 루프/로깅)