Files
multi-agent-mux/NATS_REPORT.md
T
Godopu a9934ad104 docs(messaging): add NATS vs MQTT feasibility report, private broker guide, and update IMPROVEMENTS backlog
- Synthesize collaborative multi-agent architectural analysis in NATS_REPORT.md
- Establish Option C: retain MQTT client protocol while adopting nats-server as dedicated broker
- Add private server deployment and configuration guide in PRIVATE_SERVER.md
- Update IMPROVEMENTS.md with latent defect findings (B-14, B-15, B-16, O-5) and 4-track priority roadmap
- Archive durable loop planning and review reports in .agents/reports/
2026-08-20 10:58:02 +09:00

13 KiB

📊 MAM 메시징 백플레인 아키텍처 심층 분석 보고서: MQTT vs NATS

  • 문서 버전: Rev.2 Final Synthesis (f1956d2e / 5ac88ca0)
  • 작성/검토 주체: MAM Multi-Agent Orchestration Team (claude, agy)
  • 기준 커밋: ac82f9b (refactor, 276/276 tests passing)
  • 문서 목적: MAM 프레임워크의 메시징 인프라(MQTT)를 NATS로 전면 전환할 것인지 여부에 대한 종합적인 기술·운영·보안 타당성 분석 및 실행 로드맵 확정.

0. 최종 판정 (Executive Verdict)

🎯 [VERDICT: DO NOT MIGRATE CLIENT PROTOCOL — ADOPT nats-server AS DEDICATED BROKER]

클라이언트 전송 프로토콜(MQTT)은 유지하고, 전용 브로커로서 nats-server의 내장 MQTT 3.1.1 어댑터를 채택합니다.

0.1 3대 핵심 근거 요약

[MAM Control Plane]  ────> run_loop.sh (wait_for_job: 3s Local Disk Polling)  ──> 100% Broker-Independent
[Observability Plane] ────> publish_event.py ──(MQTT 3.1.1)──> nats-server (JetStream + nkeys)
  1. 제어 평면과 관측 평면의 분리: MAM의 핵심 루프(run_loop.sh)는 MQTT 메시지를 구독하지 않으며, 로컬 파일시스템(.mam/jobs/<id>.json)을 3초 주기로 폴링(wait_for_job)하여 작업 완료를 판정합니다. 브로커는 비동기 관측(observability) 사이드카이며 제어 평면을 차단하지 않습니다.
  2. NATS의 실질적 이점은 '서버'에 존재: NATS의 핵심 강점(단일 무의존 Go 바이너리, JetStream 영속성, nkeys/JWT 계정·Subject별 ACL)은 서버 계층의 속성입니다. nats-serverMQTT 3.1.1 프로토콜을 네이티브로 수용하므로, 클라이언트 코드를 한 줄도 바꾸지 않고 서버의 모든 운영·보안 이점을 100% 확보할 수 있습니다.
  3. 네이티브 NATS(nats-py) 전환의 비용 대비 무익함: nats-py는 asyncio 전용 라이브러리로, bash 기반의 단명(short-lived) 동기 CLI 도구들(publish_event.py 등)과 심각한 구조적 마찰을 일으키며, 최소 46건의 테스트 재작성 및 276건 green 베이스라인 훼손 위험을 초래합니다. 반면 NATS 고유 기능(Req/Reply, 초당 수백만 처리량, 클러스터링)은 MAM 워크로드(단일 워크스페이스, 잡당 수 개 이벤트)에서 전혀 사용되지 않습니다.

1. 3대 전략적 선택지 비교 분석

평가 항목 (A) 현행 유지 (공개 HiveMQ) (B) 네이티브 NATS 전환 (nats-py) (C) nats-server + MQTT 프로토콜 유지 (채택안)
클라이언트 코드 변경 0줄 4개 호출부 전면 비동기 재작성 0줄 (환경변수만 구성)
테스트 코드 재작성 0건 최소 46건 재작성 (276건 베이스라인 위험) 0건 (기존 276건 100% 보존)
A-2 보안 결함 해소 불가 (공개 브로커) 완전 해소 완전 해소 (nkeys/JWT subject ACL)
단일 정적 바이너리 배포 불가 지원 지원 (nats-server 바이너리 1개)
이벤트 영속성 (JetStream) 미지원 지원 지원 (내장 JetStream 엔진)
동기 CLI 호환성 우수 (paho-mqtt) 심각 (asyncio 강제) 우수 (기존 동기 핫패스 유지)
되돌리기(Rollback) 비용 🔴 높음 (비가역 코드 재작성) 🟢 0 (가역적 환경변수 스위치)
최종 평가 기각 (보안 위험) 기각 (비용 대비 실익 전무) 🏆 최종 채택

2. 현행 아키텍처 실측 및 기술적 진단 (Ground Truth)

2.1 제어 경로 상의 MQTT 의존도 실측

  • run_loop.sh (899줄, 메인 오케스트레이터) 내 MQTT 직접 참조는 :889의 임시 구독자 로그 파일 삭제 1건뿐입니다.
  • 작업 완료 감지는 11개 호출 지점(전체 12개 참조) 전체가 wait_for_job() 함수를 통해 .mam/jobs/<id>.json 파일의 status 필드를 3초 간격으로 검사합니다.
  • 따라서 브로커가 다운되어도 제어 평면 자체는 독립적으로 완주할 수 있는 구조입니다.

2.2 paho-mqtt 결합도 (Blast Radius)

  • import pahomqtt_common.py:32 단 1곳에 캡슐화되어 있습니다.
  • 그러나 make_client()가 raw mqtt.Client 인스턴스를 반환하여 다음 4개 지점에서 구동됩니다:
    1. mqtt_common.py:250-276 (make_client)
    2. publish_event.py:102-122 (발행 및 ACK 대기)
    3. job_subscriber.py:172-251 (이벤트 큐잉 및 구독)
    4. reconcile.sh:245-292 (내장 python 이벤트 수신)

3. 코드베이스 잠복 결함 분석 (F-1 ~ F-5)

브로커 제품 선택과 무관하게 현행 코드에 잠복해 있는 5가지 구조적 결함이 발굴되었습니다.

[발굴된 결함 체인]
F-1: publish 실패 시 return 2 ──> 레지스트리 상태 동기화 누락 ──> run_loop 3900초(65분) 정지
F-4: subscriber 미포착 예외 rc=1 ──> loop/discuss 경로에서 job_status="error" 오판정
F-2/F-3: 전역 토픽 + auth_token 조건부 ──> 워크스페이스 격리 및 HMAC 검증 사각지대 (A-2)
F-5: 매 실행 랜덤 client_id ──> 문서가 주장하는 durable session 구성 불가

3.1 F-1 (Critical): 발행 실패 시 레지스트리 갱신 누락 (65분 루프 정지)

  • publish_event.py:195-199에서 브로커 네트워크 오류 발생 시 return 2로 조기 종료됩니다.
  • 이로 인해 뒤따르는 append_event, registry.append_event, update_job_status(status=completed)가 실행되지 못합니다.
  • wait_for_jobstatus=running 상태에서 max_wait=3900s를 소진할 때까지 65분간 정지합니다.

3.2 F-2 (High): 워크스페이스 지문 토픽 미발행 (A-2)

  • reconcile.sh:236은 지문 토픽(mam/<fp>/jobs/+/events)을 구독하지만, mqtt_common.py:119는 전역 토픽(python/mqtt/jobs)으로만 발행합니다.
  • 워크스페이스 간 메시지 격리가 실질적으로 비활성화되어 있습니다.

3.3 F-3 (High): HMAC 인증 조건부 공허화 (A-2)

  • registry.py:75-79는 TLS나 사용자 인증이 켜진 보안 브로커 감지 시 secrets.token_urlsafe(32)를 자동 생성하나, 기본 공개 브로커(또는 평문 TCP 브로커) 환경에서는 토큰이 발급되지 않아 auth_token=None으로 남습니다.
  • 이로 인해 verify_hmacif not auth_token: return True 분기가 무조건 참이 되어 공개 브로커 환경에서 HMAC 검증이 무력화됩니다 (Track 2에서 전 브로커 대상 무조건 발급으로 심층 방어 적용 필요).

3.4 F-4 (Critical): loop/discuss 위임 경로의 오판정 결함

  • multi-agent-mux-delegate-job:331-341에서 wait "$sub_pid"sub_rc를 직접 job_status로 매핑(rc=1 -> job_status="error").
  • 브로커 연결 실패 시 job_subscriber.py가 미포착 예외로 rc=1을 내므로, 브로커 접속 실패가 작업 에러로 둔갑합니다.

3.5 F-5 (Medium): 영속 세션(Durable Session) 구성 불가

  • make_client()가 매 실행마다 uuid.uuid4().hex[:8]로 랜덤 client_id를 생성하므로, 브로커가 재연결 세션을 식별할 수 없습니다.

4. 리뷰 및 챌린지 검증 (Challenge Resolution)

4.1 C1 챌린지 분석 및 판정

Reviewer (agy)가 제기한 job_subscriber.py의 제어 경로 블로킹 및 디스크 폴백 누락 지적을 실측 검증하였습니다.

  1. C1-a (위임 대기 경로 실재): multi-agent-mux-delegate-job:227wait "$sub_pid"가 존재하며, run_loop.sh의 모든 호출부가 --type direct로 이 경로를 통과함을 확인 (수용).
  2. C1-b (디스크 폴백 부재): job_subscriber.py는 오직 watcher.events.get()만 대기하므로 브로커 단절 시 이벤트를 수신하지 못함 (수용).
  3. C1-c (메커니즘 선후관계): C1은 F-1이 해결되어 디스크에 완료 상태가 쓰여진 이후에 드러나는 연쇄 결함임 (정정 및 반영).
  4. C1-d (지연 시간 실측): 브로커 도달 불가 시 구독자는 15~40초 내 rc=1로 조기 종료되어 실제 추가 블로킹은 0초임 (지연 영향 기각, 그러나 감사 로그 오염 및 거짓 실패 판정의 심각성으로 채택).
  5. C1-e (해결책 채택): job_subscriber.py의 대기 루프에 로컬 디스크(load_job / read_logged_status) 폴백을 도입하여 브로커 단절 시에도 즉시 정상 종료하도록 보강.

5. 단계별 실행 계획 (Actionable Roadmap)

[Track 0: 결함 교정] ──> [Track 1: nats-server 스파이크] ──> [Track 2: A-2 보안/격리 해소] ──> [Track 3: 문서화]
(F-1, C1, F-4 해결)     (S-1 ~ S-9 매트릭스 검증)         (F-2, F-3, Token, ACL)       (MESSAGING, VERSIONS)

5.1 Track 0 — 가용성 및 결함 교정 (최우선 과제, 브로커 무관)

Step 순서 의존성 (Strict Ordering)

  1. Step 1 (publish_event.py): 발행 실패 시에도 레지스트리 상태 동기화 및 감사 로그 작성을 완수하고 return 2 반환.
  2. Step 2 (job_subscriber.py): queue.Empty 시 3초 스로틀로 디스크 터미널 상태를 확인하여 source: disk-fallback 합성 이벤트 출력 후 rc=0 조기 종료.
  3. Step 3 (multi-agent-mux-delegate-job): 인프라 예외에 전용 rc=3을 부여하고 job_status="broker_unavailable" 분기 처리.

회귀 가드 매트릭스 (11종 신설 — G-1 ~ G-11, 목표 287/287 PASS)

  • G-1: 브로커 도달 불가 발행 시 rc=2이면서 레지스트리 status=completed 확인.
  • G-2: 감사 로그에 published: falsepublish_error 필드 기록 확인.
  • G-3: 정상 브로커 발행 시 rc=0published: true 무회귀 확인.
  • G-4: 발행 실패 시 단조 last_seq 증가 및 후속 발행 seq 보존 확인.
  • G-5: 디스크 status=completed 선작성 시 브로커 다운 상태에서도 job_subscriber.py가 3초 내 rc=0 종료.
  • G-6: 디스크 폴백 종료 시 stdout에 disk-fallback 명시 확인.
  • G-7: 디스크 status=error 시 폴백 rc=1 반환 확인.
  • G-8: 다중 잡 감시 시 전체 완료 전까지 조기 종료 방지.
  • G-9: 디스크 터미널 부재 + 브로커 실패 시 rc=3 반환 확인 (F-4 방어).
  • G-10: loop 위임 경로에서 rc=3 수신 시 job_status"error"로 오판되지 않음을 확인.
  • G-11: registry.register_job() 호출 시 auth_token이 항상 비어있지 않게 생성됨을 단언 (secrets.token_urlsafe(32) 유지).

5.2 Track 1 — nats-server 스파이크 검증 매트릭스 (S-1 ~ S-9)

격리 클론($SCRATCH/nats-spike)에서 검증 수행:

  • S-1: nats-server -js MQTT 리스너 기본 구동 및 started/progress/completed 발행 수용 (rc=0).
  • S-2: paho-mqtt 2.x CallbackAPIVersion.VERSION2 CONNACK 호환성 검증.
  • S-3 (핵심 관문): Retained terminal event 정상 전달 검증 (늦은 구독자의 즉시 최종 상태 수신). 실패 시 mosquitto로 회귀.
  • S-4: QoS 1 wait_for_publish ACK 동작 검증.
  • S-5: 와일드카드 토픽(mam/<fp>/jobs/+/events) 구독 및 라우팅 검증.
  • S-6: TLS 암호화 및 유저 인증 접근 제어 검증.
  • S-7: Subject/Topic 레벨 권한 분리(Publisher write-only / Subscriber read-only) 검증.
  • S-8: 전체 287건 회귀 테스트 100% PASS 검증.
  • S-9: Track 0 디스크 폴백이 nats-server 장애 상황에서도 정상 동작함을 통합 검증.

5.3 Track 2 — A-2 보안 및 워크스페이스 격리 해소

  1. F-3 해소: registry.register_job()에서 브로커 설정(TLS/인증 유무)과 무관하게 secrets.token_urlsafe(32) 기반 auth_token무조건 항상 발급.
  2. F-2 해소: DEFAULT_TOPIC_ROOTmam/<sha256[:12]>/jobs로 전환. 발행측 전환 후 reconcile.sh의 레거시 구독 단계적 제거.
  3. 배포 설정에 nkeys 기반 계정 분리 적용.

5.4 Track 3 — 문서 및 설정 동기화

  • MESSAGING.md: 브로커 사양을 nats-server로 갱신, F-1/C1 해소 기록, F-5 실측에 맞춘 영속 세션 설명 정정.
  • IMPROVEMENTS.md & VERSIONS.md: A-2 완료 전환, F-1/F-4/F-5 백로그 이력 반영.
  • .mam.env: nats-server 포트(1883/8883) 및 인증 템플릿 갱신.

6. 비-목표 (Explicit Non-Goals)

  1. nats-py 라이브러리 도입 및 클라이언트 비동기 재작성: 불필요한 복잡도 및 장애 유발.
  2. .mam/jobs/*.json의 JetStream KV 대체: 파일시스템 폴링 제어 계약을 훼손하므로 상태 계층 변경 제외.
  3. Durable Session 강제 도입을 위한 client_id 고정: 동시성 충돌 위험이 크며, Track 0 디스크 폴백이 동일 복원력을 무비용으로 제공함.
  4. requirements.txtpaho-mqtt>=2.0.0 제거: 현행 종속성 유지.

7. 결론

MAM 프레임워크의 메시징 백플레인은 **클라이언트 프로토콜(MQTT)을 100% 보존한 상태에서 nats-server를 전용 브로커로 채택(Option C)**하는 것이 기술적·운영적·보안적 최적해입니다.

선행 필수 과제인 **Track 0(F-1 + C1 + F-4 가용성 결함 교정)**을 우선 완수한 후, 스파이크 검증(Track 1) 및 A-2 보안 강화(Track 2)를 순차 전개합니다.