feat(multi-instance): dcy-009 BAT-013 — RabbitMQ 다중 인스턴스 토폴로지 (REQ-048~051)

dcy-009(다중 인스턴스 RabbitMQ 토폴로지) 전체 SDLC: draft-req → confirm-req →
draft-batch(BAT-013) → run-batch-delivery → confirm-req-implemented.

- REQ-048 (Configuration): DaemonInstanceConfig.from_toml + DaemonConfig.from_config
  (env 병합, portfolio_hub_token 주입) + 데몬 --config 필수 argparse + .gitignore 인스턴스
  TOML + config/instance.example.toml
- REQ-049 (Interface): topology 동적 큐명 빌더 intent/snapshot/control_queue(instance_key)
  — default=리터럴 큐명(하위호환), named=`{base}.{instance_key}` + daemon per-instance 구독
- REQ-050 (Integration): trex.direct(topic)/trex.broadcast(fanout) exchange +
  declare_topology(instance_key) per-instance binding + control dual binding + direct_routing_key
- REQ-051 (Documentation): interface-contract/runtime-flows/project-structure 정합

하위호환: instance_key="default" → 기존 리터럴 큐명·RPC 경로 무회귀(FR-5).
split-brain 방지(NFR-3): 인스턴스 전용 큐. 민감값(NFR-4): TOML + gitignore.

검증: 신규 9 단위 + RabbitMQ 라우팅 2, 단위 271 passed,
인프라 게이트 301 passed(directed 격리 S-2 + broadcast dual S-3 + default 무회귀 S-4),
ruff clean. REQ-048~051 Implemented, BAT-013 release-candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-07 15:10:36 +09:00
co-authored by Claude Opus 4.8
parent 7e23648489
commit 9f83c64a40
24 changed files with 1223 additions and 85 deletions
@@ -0,0 +1,51 @@
# BAT-013 Design
- Status: confirmed
- Batch: BAT-013
## Architecture Summary
다중 인스턴스가 동일 RabbitMQ 에서 split-brain 없이 운영되도록 토폴로지·설정을 인스턴스화한다.
1. **동적 큐명**(`topology.py`): `intent_queue/snapshot_queue/control_queue(instance_key)`. `instance_key in (None,"default")` → 기존 리터럴(`trex.intent.req` 등), 그 외 → `{base}.{instance_key}`. 기존 상수는 default 값으로 보존(하위호환).
2. **exchange**(`topology.py`+`rabbitmq.py`): `trex.direct`(topic,durable) + `trex.broadcast`(fanout,durable). `declare_topology(instance_key)` 가 인스턴스 큐를 direct 에 `{instance_key}.{intent|control|snapshot}` 로 바인딩 + control 큐를 broadcast 에 dual 바인딩.
3. **설정**(`config.py`): `DaemonInstanceConfig(instance_key, portfolio_hub_token)` + `from_toml(path)`(tomllib) + `DaemonConfig.from_config(toml_path)` 가 `from_env()` 결과에 TOML 을 병합(`portfolio_hub_token` → PortfolioHubConfig.token).
4. **진입점**(`daemon.main`): argparse `--config` 필수. instance_key 를 declare/consume 에 일관 전달.
## Changed Areas
- Docs: interface-contract.md(routing key)·runtime-flows.md(TOML 기동/라우팅)·project-structure.md(동적 큐명/설정/exchange) — REQ-051.
- Modules:
- `trex_core/adapters/messaging/topology.py` — 큐명 빌더 + DIRECT/BROADCAST exchange 상수 + `direct_routing_key`
- `trex_core/adapters/messaging/rabbitmq.py` — `declare_topology(instance_key="default")` + exchange 선언 + binding(direct per-instance + control dual) + (필요시) broadcast publish helper
- `trex_core/runtime/config.py` — `DaemonInstanceConfig` + `from_toml` + `from_config`
- `trex_core/runtime/daemon.py` — `--config` argparse(main) + instance_key 보관 + consumer 큐명에 instance_key 적용 + declare_topology(instance_key)
- `.gitignore` — 인스턴스 TOML 패턴
- Interfaces:
- `intent_queue(instance_key) -> str` 등 (default 하위호환)
- `declare_topology(instance_key="default")`
- `direct_routing_key(instance_key, channel_type) -> "{instance_key}.{channel_type}"`
- `DaemonInstanceConfig.from_toml(path)`, `DaemonConfig.from_config(toml_path)`
## Key Decisions
1. **default = 리터럴 큐명(특수 처리)**: FR-2(suffix)와 FR-5(default 무회귀)의 긴장을 해소 — named instance 만 suffix, default 는 기존 이름 유지. 기존 messaging 테스트/배포 무변경. (planning R-1)
2. **declare_topology 기본 인자 `instance_key="default"`**: 기존 호출부(`transport.declare_topology()`)가 인자 없이도 default 동작 → 호출부 광범위 수정 회피.
3. **exchange 는 항상 선언(default 포함)**: direct/broadcast 는 default 인스턴스에서도 선언·바인딩하되, 기존 직접 큐 reply-to RPC 경로는 그대로 동작(추가 바인딩은 비파괴적). 기존 RPC 회귀 없음(FR-5/AC-6).
4. **TOML 우선 병합**: `from_config` 는 `from_env()` 로 베이스 구성 후 TOML 의 `portfolio_hub_token` 을 PortfolioHubConfig.token 으로 override. `instance_key` 는 TOML 전용.
5. **`--config` 필수**: argparse required. 누락 시 argparse 가 usage 출력 + exit≠0(OQ-2). 단, `from_env()` 단독 호출(테스트/라이브러리)은 계속 가능.
6. **pika per-thread**: 바인딩 추가만, consumer 스레드/커넥션 수 불변(NFR-2).
## Edge Cases
- default 큐명 충돌 없음: `*.default` 를 쓰지 않고 리터럴 사용.
- TOML 누락 필드/파일 부재 → 명확한 예외(FR-1/AC-4).
- broadcast 중복 control 전달 → 핸들러 idempotency(REQ-036/advisory lock, R-3).
- 기존 old 큐 잔류 → 자연 소멸(OQ-1), 코드 무관.
- snapshot/intent reply-to RPC: 클라이언트가 direct routing key 로 보내든 직접 큐로 보내든, 인스턴스 큐가 direct 바인딩 + 직접 publish 둘 다 수신 가능(하위호환).
## Architecture Impact
- runtime-flows: TOML 기동 + directed/broadcast 라우팅 + split-brain 방지 — REQ-051.
- project-structure: topology 동적화 + 설정 계층 + exchange — REQ-051.
- interface-contract: routing key 계약 — REQ-051.
## Reference Doc Update Plan
- interface-contract.md: routing key(`{instance_key}.{channel_type}`)+broadcast+큐명 규칙 (REQ-051 AC-1)
- runtime-flows.md: `--config` 기동 + directed/broadcast + split-brain (REQ-051 AC-2)
- project-structure.md: 동적 큐명/DaemonInstanceConfig/`--config`/exchange (REQ-051 AC-3)