# Stats API 일원화 구현 완료 ## 개요 - **일시**: 2025-09-20 - **작업자**: Claude Code - **목표**: rb8001과 robeing-monitor의 중복된 Stats API를 robeing-monitor로 일원화 - **결과**: ✅ 성공 ## 문제 상황 ### 1. 중복 구현 - **rb8001**: 자체 stats 관리 (`/stats`, `/api/stats/{robeing_id}`) - **robeing-monitor**: 별도 stats 관리 (`/api/stats/{robeing_id}`) - 두 서비스가 동일한 DB 테이블(`robeing`)을 다른 스키마로 접근 ### 2. 스키마 불일치 - **rb8001 ORM 모델**: `robeing_id` 필드 (실제 DB에 없음) - **robeing-monitor ORM 모델**: `robeing_container_id` 필드 (실제 DB와 일치) - **실제 DB 테이블**: UUID primary key, `robeing_container_id` 사용 ### 3. 데이터 불일치 위험 - 두 서비스가 독립적으로 stats 관리 - 동일 테이블에 대한 경합 조건 발생 가능 - 데이터 일관성 보장 불가 ## 해결 방안 ### 전략: robeing-monitor를 단일 소스로 통합 robeing-monitor가 이미 실제 DB 스키마와 일치하고, 여러 서비스에서 사용 가능하므로 Stats 관리의 단일 소스로 지정 ## 구현 내용 ### 1. rb8001 환경변수 설정 ```bash # /home/admin/ivada_project/rb8001/.env STATE_SERVICE_URL=http://localhost:9024 ``` ### 2. rb8001 /stats 엔드포인트 수정 ```python # main.py @app.get("/stats") async def get_stats(): """현재 로빙의 스탯 조회 - robeing-monitor에서 가져옴""" try: async with httpx.AsyncClient() as client: response = await client.get( f"{settings.STATE_SERVICE_URL}/api/stats/{settings.ROBEING_ID}", timeout=5.0 ) if response.status_code == 200: data = response.json() return { "robeing_id": settings.ROBEING_ID, "stats": { "memory": data.get("memory", 10), "compute": data.get("compute", 10), "react": data.get("react", 10), "empathy": data.get("empathy", 10), "leadership": data.get("leadership", 10) }, "level": data.get("level", 1) } ``` ### 3. rb8001 초기화 로직 수정 ```python # app/router/router.py async def _load_stats_from_state(self): """State Service(robeing-monitor)에서 스탯 로드""" try: if not settings.STATE_SERVICE_URL: logger.warning("STATE_SERVICE_URL not configured") return async with httpx.AsyncClient() as client: response = await client.get( f"{settings.STATE_SERVICE_URL}/api/stats/{settings.ROBEING_ID}", timeout=5.0 ) if response.status_code == 200: data = response.json() from app.brain.stats_manager import RobeingStats self.stats = RobeingStats( memory=data.get("memory", 10), compute=data.get("compute", 10), react=data.get("react", 10), empathy=data.get("empathy", 10), leadership=data.get("leadership", 10) ) logger.info(f"Stats loaded from robeing-monitor: {self.stats}") ``` ### 4. rb8001 중복 코드 제거 ```python # app/state/state_service.py # stats 관련 엔드포인트 주석 처리 # @app.get("/api/stats/{robeing_id}") # @app.put("/api/stats/{robeing_id}") ``` ### 5. robeing-monitor PUT 엔드포인트 활성화 ```python # /home/admin/ivada_project/robeing-monitor/app/main.py # State Service 엔드포인트 마운트 추가 app.mount("/api", state_service.app) ``` ## 테스트 결과 ### 1. GET 테스트 ```bash # robeing-monitor 직접 조회 curl http://localhost:9024/api/stats/rb8001 # 결과: {"memory": 10, "compute": 10, "react": 10, ...} # rb8001 프록시 조회 curl http://localhost:8001/stats # 결과: {"robeing_id": "rb8001", "stats": {...}, "level": 3} ``` ### 2. PUT 테스트 ```bash # Stats 업데이트 curl -X PUT http://localhost:9024/api/stats/rb8001 \ -H "Content-Type: application/json" \ -d '{"memory": 15, "compute": 12}' # rb8001에서 확인 curl http://localhost:8001/stats # 결과: 업데이트된 값 반영 확인 ``` ## 효과 ### 1. 데이터 일관성 - 단일 소스(robeing-monitor)에서만 stats 관리 - 데이터 불일치 문제 해결 ### 2. 유지보수성 - 중복 코드 제거 - 명확한 책임 분리 ### 3. 확장성 - 다른 서비스도 robeing-monitor API 사용 가능 - 중앙 집중식 stats 관리 ## 주의사항 1. **DB 스키마**: 실제 테이블은 `robeing_container_id` 사용 (not `robeing_id`) 2. **Primary Key**: UUID 타입 (not Integer) 3. **환경변수**: `STATE_SERVICE_URL` 필수 설정 4. **의존성**: robeing-monitor 서비스가 먼저 실행되어야 함 ## 교훈 1. **DB 스키마 우선 확인**: ORM 모델 작성 전 실제 테이블 구조 확인 필수 2. **서비스 책임 명확화**: 동일 기능 중복 구현 방지 3. **API 경로 일관성**: GET과 PUT이 동일한 경로 패턴 사용하도록 설계 4. **통합 테스트 중요성**: 단순 조회뿐만 아니라 업데이트 기능도 테스트 ## 관련 파일 - `/home/admin/ivada_project/rb8001/main.py` - `/home/admin/ivada_project/rb8001/app/router/router.py` - `/home/admin/ivada_project/rb8001/app/state/state_service.py` - `/home/admin/ivada_project/robeing-monitor/app/main.py` - `/home/admin/ivada_project/robeing-monitor/app/state/state_service.py`