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)
@@ -0,0 +1,45 @@
# BAT-013 Implementation
- Status: completed
- Batch: BAT-013
## Plan Summary
- 순서: REQ-049 동적 큐명/declare_topology → REQ-050 exchange/binding/routing → REQ-048 TOML 로더/--config → REQ-051 baseline. 각 단계 단위 green 후 진행. default 하위호환 회귀 우선.
## Changed Files
- (구현 시 기록)
### 신규 (예정)
- `tests/runtime/test_instance_config.py` (REQ-048)
- `tests/adapters/messaging/test_instance_topology.py` (REQ-049/050)
- 인스턴스 TOML 예시(`config/instance.example.toml`, 추적) (REQ-048)
### 수정 (예정)
- `trex_core/adapters/messaging/topology.py` (동적 큐명 + DIRECT/BROADCAST + routing key)
- `trex_core/adapters/messaging/rabbitmq.py` (declare_topology(instance_key) + exchange/binding)
- `trex_core/runtime/config.py` (DaemonInstanceConfig + from_toml + from_config)
- `trex_core/runtime/daemon.py` (--config argparse + instance_key 큐 적용)
- `.gitignore` (인스턴스 TOML)
- baseline 3문서 (REQ-051)
## Changed Files (실제)
- 신규: `config/instance.example.toml`(추적), `tests/runtime/test_instance_config.py`, `tests/adapters/messaging/test_instance_topology.py`.
- 수정: `topology.py`(동적 큐명 빌더 + DEFAULT_INSTANCE_KEY/DIRECT_EXCHANGE/BROADCAST_EXCHANGE + direct_routing_key), `rabbitmq.py`(declare_topology(instance_key) + exchange/binding/control dual + import 정리), `config.py`(DaemonInstanceConfig+from_toml+from_config, DaemonConfig.instance_key), `daemon.py`(--config argparse main + per-instance 큐 구독 + declare_topology(instance_key)), `.gitignore`(인스턴스 TOML), baseline 3문서(REQ-051).
## Execution Log
1. REQ-049 동적 큐명(default 리터럴) + declare_topology(instance_key) + daemon per-instance consume → 빌더 단위 + messaging 회귀 green.
2. REQ-050 trex.direct/trex.broadcast exchange + binding + routing key → 인프라 게이트(directed 격리 + broadcast dual) green.
3. REQ-048 DaemonInstanceConfig.from_toml + from_config 병합 + --config argparse + .gitignore + example TOML → 로더 단위 + `--config` 누락 subprocess 종료 green.
4. REQ-051 baseline 3문서 정합.
5. 전체 회귀: 단위 271 passed / 30 skipped, ruff clean.
6. 인프라 게이트(PG+RabbitMQ): 301 passed (directed 라우팅 격리 S-2 + broadcast dual S-3 + default 무회귀 S-4 포함).
## Validation
- 단위: `pytest -q --deselect ...::test_sandbox_token_and_submit` → 271 passed / 30 skipped.
- 인프라 게이트: `TREX_RABBITMQ_TEST_URL=... TREX_POSTGRESQL_TEST_DSN=... pytest -q` → 301 passed.
- lint: `ruff check trex_core tests` → All passed.
## Remaining Risks
- R-1(declare_topology 변경 회귀): default=리터럴 큐명 하위호환으로 기존 messaging 테스트 무변경 — 301 passed 로 확인(FR-5/NFR-1).
- R-2(old 큐 잔류): 운영 자연 소멸(OQ-1), 코드 무관.
- (이월, 비차단) 실 2-인스턴스 동시 기동 manual e2e: directed 격리·broadcast 는 인프라 게이트 통합 테스트로 검증됨. 실 배포 시 다중 인스턴스 운영 시점 확인.
@@ -0,0 +1,33 @@
# BAT-013: 다중 인스턴스 RabbitMQ 토폴로지 — TOML config + instance_key 큐 + direct/broadcast exchange
- Status: release-candidate
- Profile: standard
- Source Discovery: dcy-009
- Included REQ: REQ-048, REQ-049, REQ-050, REQ-051
## Summary
- dcy-009(다중 인스턴스 RabbitMQ 토폴로지)를 단일 standard batch 로 delivery 한다. per-instance TOML 설정 로더 + `--config` 필수(REQ-048), `instance_key` 기반 동적 큐명 + default 하위호환(REQ-049), `trex.direct`(topic)/`trex.broadcast`(fanout) exchange + 바인딩(REQ-050), baseline 3문서 정합(REQ-051). 다중 인스턴스를 동일 RabbitMQ 에 붙여도 인스턴스 전용 큐로 split-brain(WatchdogStore/SafetySellService 경쟁 소비)을 구조적으로 방지한다.
## Scope
- In Scope:
- `DaemonInstanceConfig.from_toml` + `--config` 필수 진입점 + env 병합 + `.gitignore` TOML (REQ-048)
- `topology.py` 동적 큐명 빌더(`intent_queue/snapshot_queue/control_queue(instance_key)`, default=리터럴 하위호환) + `declare_topology(instance_key)` + daemon per-instance consume (REQ-049)
- `trex.direct`(topic) + `trex.broadcast`(fanout) exchange + per-instance/control dual binding + routing key 빌더 (REQ-050)
- baseline 3문서(interface-contract/runtime-flows/project-structure) (REQ-051)
- Out of Scope:
- strategy 모듈(orb-v1) publish 코드 변경 — 계약만 정의
- WatchdogStore Redis 외부화 / leader election / failover
- 전체 env 의 TOML 일원화(KIS/DSN/RABBITMQ_URL 은 `.env` 유지)
- old 큐 자동 삭제 스크립트(운영 절차)
## Documents
- Planning: [planning.md](./planning.md)
- Design: [design.md](./design.md)
- Implementation: [implementation.md](./implementation.md)
- Verification: [verification.md](./verification.md)
## Notes
- Source discovery 단일(dcy-009). OQ-1(old 큐 자연 소멸)·OQ-2(`--config` 필수) Answered.
- 하위호환 핵심(REQ-049): `instance_key="default"` → 리터럴 큐명 → 기존 테스트/배포 무회귀(FR-5/NFR-1).
- NFR-2(pika per-thread) 유지, NFR-3(split-brain) 인스턴스 전용 큐로 달성, NFR-4(민감값) TOML+gitignore.
- multi-instance 라우팅(directed/broadcast)·split-brain 격리는 인프라 게이트(RabbitMQ) 통합/e2e 로 검증.
@@ -0,0 +1,48 @@
# BAT-013 Planning
- Status: confirmed
- Batch: BAT-013
- Profile: standard
## Included REQ
- REQ-048 (Configuration): `DaemonInstanceConfig.from_toml` (tomllib) + `--config` 필수 진입점 + `from_config` env 병합(`portfolio_hub_token` 주입) + `.gitignore` TOML + 토큰 로그 미노출.
- REQ-049 (Interface): `topology.py` 동적 큐명 빌더(default=리터럴 하위호환) + `declare_topology(instance_key)` + daemon intent/snapshot/control consumer per-instance 큐 구독.
- REQ-050 (Integration): `trex.direct`(topic)/`trex.broadcast`(fanout) exchange + per-instance direct binding + control dual binding + `direct_routing_key` 빌더.
- REQ-051 (Documentation): interface-contract(routing key)/runtime-flows(TOML 기동·라우팅)/project-structure(동적 큐명·설정 계층·exchange) 정합.
## Delivery Plan
1. REQ-049: `topology.py` 큐명 빌더(default 하위호환) → `rabbitmq.declare_topology(instance_key)` → daemon consumer per-instance. 큐명 단위 + 기존 회귀.
2. REQ-050: `trex.direct`/`trex.broadcast` exchange 선언 + binding(direct per-instance, control dual) + routing key 빌더. 토폴로지 단위 + 인프라 게이트(라우팅 격리/broadcast).
3. REQ-048: `DaemonInstanceConfig.from_toml` + `--config` argparse 진입점 + `from_config` 병합 + `.gitignore` + 토큰 미노출. 로더 단위.
4. REQ-051: baseline 3문서 정합.
5. 전체 회귀 + ruff + 인프라 게이트(default 무회귀 + 라우팅) + (가능 시) 2-인스턴스 e2e.
## Dependencies
- Internal:
- dcy-006/BAT-007 messaging(topology/rabbitmq/daemon consumer) — 변경 대상 기반.
- BAT-012 control 채널(`trex.control.req`) — control 큐가 broadcast dual binding 대상.
- BAT-008 PortfolioHubConfig.token — TOML 주입 지점.
- External:
- RabbitMQ(topic/fanout exchange). stdlib `tomllib`(py3.12).
## Design Gate
- Design Required Before Implementation: yes
- Reason: (a) `declare_topology` signature 변경 + 큐명 동적화는 **모든 messaging consumer/test 표면**에 영향 — default 하위호환 규칙(리터럴 vs suffix)을 설계에서 고정해야 회귀를 막는다. (b) topic/fanout exchange + per-instance/dual binding 의 정확한 binding 매트릭스와 routing key 계약을 설계에서 확정해야 strategy 계약과 일치한다. (c) TOML+env 병합 우선순위(`portfolio_hub_token` TOML 우선) + `--config` 진입점 구조를 설계에서 명시. → design.md 동일 턴 scaffold.
## Milestones
1. M1 — REQ-049 동적 큐명 + declare_topology(instance_key) + default 회귀 green
2. M2 — REQ-050 exchange + binding + routing key + 인프라 게이트(라우팅) green
3. M3 — REQ-048 TOML 로더 + --config + 병합 + .gitignore 단위 green
4. M4 — REQ-051 baseline 3문서
5. M5 — 전체 회귀 + ruff + 인프라 게이트 + 2-인스턴스 e2e(가능 시)
## Risks
- R-1 (Medium): `declare_topology`/큐명 변경이 기존 messaging 테스트 다수에 영향. 대응: default=리터럴 큐명 하위호환으로 기존 테스트 무변경 유지(REQ-049 핵심). 빌더에 default 특수 처리.
- R-2 (Medium): old durable 큐 잔류(dcy-009 R-2). 대응: 자연 소멸 허용(OQ-1), 배포 절차 외. 코드 무관.
- R-3 (Low): broadcast 중복 제어(dcy-009 R-3). 대응: kill-switch/pause idempotency(REQ-036/advisory lock).
- R-4 (Low): TOML 토큰 커밋 노출(dcy-009 R-4). 대응: `.gitignore` + 로그 미노출(REQ-048).
- R-5 (Low): pika BlockingConnection thread-safety. 대응: per-thread connection 유지(NFR-2), 바인딩만 추가.
## Notes
- 하위호환 우선: default 인스턴스는 기존 리터럴 큐명·RPC 경로 그대로 → 기존 277+ 테스트 무회귀가 1차 게이트.
- 2-인스턴스 e2e(라우팅 격리/broadcast)는 RabbitMQ 인프라 게이트 또는 manual; 단일 인스턴스 기본 검증은 단위로 충분.
@@ -0,0 +1,64 @@
# BAT-013 Verification
- Status: verified
- Batch: BAT-013
## Acceptance Mapping
> 구현 완료 후 각 AC 에 evidence 매핑. 아래는 계획 골격.
### REQ-048 (TOML 로더 + --config)
| AC | Evidence (예정) |
|----|---------|
| AC-1 from_toml 파싱 | 로더 단위 |
| AC-2 --config 누락 종료 | 진입점 단위/검사 |
| AC-3 token 주입 | 로더 단위(S-5) |
| AC-4 필수 필드 누락 오류 | 로더 단위 |
| AC-5 .gitignore TOML | gitignore 확인 |
| AC-6 token 로그 미노출 | 코드/로그 검사 |
### REQ-049 (동적 큐명 + 하위호환)
| AC | Evidence (예정) |
|----|---------|
| AC-1 named suffix | 큐명 빌더 단위 |
| AC-2 default 리터럴 | 동일 |
| AC-3 declare named 큐 | 인프라 게이트 |
| AC-4 default 회귀 없음 | 전체 pytest |
| AC-5 per-thread/스레드 수 | daemon 검사 |
| AC-6 인스턴스 격리 | 인프라 게이트/e2e |
### REQ-050 (exchange + binding)
| AC | Evidence (예정) |
|----|---------|
| AC-1 direct/broadcast 선언 | 인프라 게이트 |
| AC-2 direct binding | 인프라 게이트 |
| AC-3 routing key 빌더 | 단위 |
| AC-4 directed 격리 | 인프라 게이트(2큐) |
| AC-5 broadcast dual 전달 | 인프라 게이트(2큐) |
| AC-6 default 회귀 | 전체 pytest |
### REQ-051 (baseline 정합)
| AC | Evidence (예정) |
|----|---------|
| AC-1 interface-contract routing key | 문서 diff |
| AC-2 runtime-flows 기동/라우팅 | 문서 diff |
| AC-3 project-structure 동적큐/설정/exchange | 문서 diff |
## Evidence
- Test:
- REQ-048 로더 6 단위(from_toml/필수 누락/token 주입 S-5/default ik/`--config` 누락 subprocess 종료/gitignore) green.
- REQ-049/050 토폴로지 3 단위(builders default vs named/routing key) + 인프라 게이트 2(directed 격리 S-2 / broadcast dual S-3) green.
- 단위 전체 회귀: `271 passed / 30 skipped`(`::test_sandbox_token_and_submit` deselect).
- 인프라 게이트(PG trex_core_test + RabbitMQ): `301 passed`(directed 라우팅 격리 + broadcast dual + default 무회귀 S-4 + 마이그레이션 round-trip 포함).
- `ruff check trex_core tests` → All passed.
- Logs: `271 passed, 30 skipped` / `301 passed ... ~15s`.
## Carried-Over / e2e
- S-1(named 큐 선언)·S-2(directed 격리)·S-3(broadcast dual): RabbitMQ 인프라 게이트 통합 테스트로 확인.
- S-4(default 무회귀): 전체 pytest 301 passed.
- S-5(TOML token 주입): 로더 단위.
- (이월, 비차단) 실 2-인스턴스 동시 기동 manual e2e: 실 다중 인스턴스 운영 시점 확인.
## Result
- Ready for Release: true
- Blocking Issues:
- 없음. 제어 큐 rename(BAT-012 R-1)·old 큐 자연 소멸(OQ-1)·strategy publish 변경(Out of Scope)·2-인스턴스 manual e2e 는 비차단(인프라 게이트 통합으로 핵심 라우팅 검증됨).
+1
View File
@@ -20,3 +20,4 @@
| BAT-010 | standard | release-candidate | REQ-034, REQ-035, REQ-036, REQ-041 | dcy-007 | [bat-010](./bat-010_20260607_safety-sell-foundation/) |
| BAT-011 | standard | release-candidate | REQ-037, REQ-038, REQ-039, REQ-040, REQ-042 | dcy-007 | [bat-011](./bat-011_20260607_safety-sell-triggers/) |
| BAT-012 | standard | release-candidate | REQ-043, REQ-044, REQ-045, REQ-046, REQ-047 | dcy-008 | [bat-012](./bat-012_20260607_kill-switch-control/) |
| BAT-013 | standard | release-candidate | REQ-048, REQ-049, REQ-050, REQ-051 | dcy-009 | [bat-013](./bat-013_20260607_multi-instance-topology/) |