Three reviewers examined fc0bd96..8def235. Codex and Claude each reproduced a defect; all three findings were re-verified here before being accepted, and none were rebutted. R1 (codex) — the shell helpers ran `python -m lib_py.delegation.cli`. For -m, sys.path[0] is the working directory, which therefore beats PYTHONPATH: with cwd inside a tree containing lib_py, the live package won even with the frozen path listed first. The documented B-13 guarantee for this path did not hold. Reproduced, then replaced with an explicit bootstrap that inserts SKILL_DIR itself. cwd is left alone, because the registry and audit-log paths are relative to it. The file-based shim path was never affected. R2 (codex) — the persistent connection was reused on host and port alone. Two jobs sharing an endpoint but carrying different credentials or TLS settings silently published over the first connection. Reproduced (the client factory ran once, under the first account, with TLS off). The guard now compares an immutable snapshot of the whole connection identity and names only the differing field, never its value. F-1 (claude) — paho queues a publish issued while disconnected and raises RuntimeError from wait_for_publish. RuntimeError is deliberately outside the retry tuple, so the one state a persistent connection exists to survive, the gap between a drop and paho's reconnect, failed on the first attempt. Confirmed against paho 2.1.0. That single condition is now translated into a retryable error; the tuple is unchanged, so genuine programming errors are still not retried. Two fixtures were hiding these: FakeInfo had no rc and never raised. It now mirrors paho's contract, which is what makes the new tests meaningful. Also folds in the duplication both Claude (O-1) and I had flagged: the CLI path kept its own copy of connect-publish-disconnect plus its own timeout constants. _publish_once now delegates to OneShotPublisher.publish_raw, so a transport fix lands once. Having two copies is exactly why these bugs existed in one of them. Documentation corrected rather than defended: the benchmark's rationale for keeping three forks was imprecise (the || true already guarantees rc 0; the lookup buys quietness and legacy parity), SKILL.md no longer claims PYTHONPATH decides the frozen path, and the daemon's directory creation is recorded as a deliberate difference. Trailing whitespace inherited by the relocated file is stripped, so the commit range is now clean too. Verified: each regression fails against the pre-fix code and passes after (R1 1, R2 4, F-1 1). 66 phase-1 tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UrDNJkHmm18uu7n7JwV5Qd
4.4 KiB
📊 Issue #5 Phase 1 — 장수 MQTT 연결 벤치마크 실측
- 문서 버전: Rev.1
- 측정 일시: 2026-09-06
- 기준 커밋: Phase 1 커밋 4 (
feat(issue-5): persistent MQTT publisher prototype and benchmark harness) - 하니스:
python -m lib_py.delegation.bench --n <N> --transport {fake|broker} - 근거 대상:
implementation_plan.md§5 "MQTT 연결 재사용 p50 단축" 항목
1. 측정 환경
| 항목 | 값 |
|---|---|
| Python | 3.9.6 (.venv) |
| paho-mqtt | 2.1.0 |
| 플랫폼 | darwin-arm64 |
| 로컬 브로커 | 없음 (mosquitto, nats-server, docker 모두 미설치) |
| 실브로커 | broker.hivemq.com:1883 (PoC 기본 공용 브로커, TLS 없음) |
⚠️ 로컬 브로커가 없으므로 실브로커 수치는 공용 인터넷 브로커에 대한 1회 측정치이며, 네트워크 상태에 좌우된다. 게이트 조건이 아니다. 게이트가 걸린 것은 아래 §2의 결정적 지표뿐이다.
2. 결정적 지표 — 가짜 전송 (CI 단정 대상)
합성 연결 지연 50 ms, N = 50.
| 지표 | OneShot | Persistent |
|---|---|---|
| 발행 수 | 50 | 50 |
| 연결 수립 횟수 | 50 | 1 |
| 실패 | 0 | 0 |
| 발행당 지연 p50 | 54.813 ms | 0.002 ms |
| 발행당 지연 p95 | 55.049 ms | 0.003 ms |
| 발행당 지연 max | 55.075 ms | 51.110 ms (최초 연결) |
tests/test_issue5_phase1_delegation.py::test_bench_fake_transport_schema_and_counts가 "OneShot = N, Persistent = 1" 및 p50 감소를 단정한다. 네트워크 접촉 0.
3. 정보성 지표 — 실브로커 (게이트 아님)
broker.hivemq.com:1883, N = 5.
| 지표 | OneShot | Persistent |
|---|---|---|
| 연결 수립 횟수 | 5 | 1 |
| 실패 | 0 | 0 |
| 발행당 지연 p50 | 1892.728 ms | 275.992 ms |
| 발행당 지연 p95 | 1942.762 ms | 875.412 ms |
| 발행당 지연 max | 1942.762 ms | 875.412 ms (최초 연결) |
p50 기준 약 85% 단축. 연결 수립이 발행 지연의 지배적 요소임을 보여준다.
3-1. 이 프로토타입의 실측 동기
Rev.1 계획 job의 started 이벤트는 payload timestamp 05:58:49Z에서 모니터 수신 05:59:01.487Z까지 약 12.5초가 걸렸고, 원인은 CONNECT_ACK_TIMEOUT=10 1회 실패 후 재연결이었다. 본 job(ddcaf930)의 started 발행에서도 같은 CONNACK 경고가 재현됐다. 1회성 연결은 이 비용을 매 이벤트마다 다시 지불한다.
4. 프로세스 포크 수 (셸 경로, 분석값)
delegate_publish_event 1회당:
| 구현 | fork |
|---|---|
레거시 (_delegate_publish_event_legacy) |
3 ($(_delegate_py_bin), $(_delegate_script), python) |
신규 (-m lib_py.delegation.cli) |
3 ($(_delegate_py_bin), $(_delegate_script), python) |
계획서 D8은 신규 경로가 2 fork일 것으로 예상했으나, 실제 구현은 3 fork로 동일하다.
_delegate_script publish_event.py조회를 유지했기 때문이다.이 조회가 실제로 보장하는 것 (교차 리뷰
cc3468cb정정): 초기 서술은 이 조회가 "비치명 계약(C-02)을 만드는 유일한 지점"이라고 했으나 부정확하다. rc 0 자체는delegate_publish_event의|| true만으로도 보장되고, C-02도 rc만 단정한다. 조회가 보장하는 것은 위임 스크립트 트리가 없을 때 인터프리터를 아예 띄우지 않아 stderr에 트레이스백을 남기지 않는다는 정숙성과, 레거시 본체와의 동작 동일성이다(인터프리터 미호출 단정은 C-01에 있다). 유지 결정 자체는 세 리뷰어 전원이 지지했으며, fork 절감은 Phase 4에서 이 계약과 함께 재검토한다.
5. 프로덕션 적용 범위
Phase 1에서 장수 연결은 프로덕션 경로에 쓰이지 않는다. CLI와 셸 헬퍼는 발행 1회 후 프로세스가 종료되므로 재사용 이득이 없고, 두 경로 모두 OneShotPublisher를 계속 쓴다. 첫 소비자는 Phase 2a의 장수 엔진이다.
6. 재현 명령
export PYTHONPATH="$PWD/.agents/skills"
.venv/bin/python -m lib_py.delegation.bench --n 50 --transport fake --connect-latency-ms 50
# 실브로커(임시 레지스트리 필요)
.venv/bin/python -m lib_py.delegation.cli submit --prompt bench --registry-dir .mam/jobs
.venv/bin/python -m lib_py.delegation.bench --n 5 --transport broker --job <id> --registry-dir .mam/jobs