# 51124 먹통: 48시간 코드 차분 기반 원인 확정 리서치 **작성일**: 2026-03-04 **범위**: `rb8001`, `robeing-gateway`, `robeing-monitor`, `skill-email`, `skill-embedding`, `skill-rag-file` 최근 48시간 커밋/디프 **목표**: 51124 CPU 폭주/먹통의 원인을 코드 근거로 확정하고, 재발 방지 해법을 운영 관점으로 고정 --- ## 1. 결론(확정) - 24 서버 먹통의 직접 경로는 `INTENT_ENGINE=graph` + `INTENT_USE_LANGGRAPH=true` 활성 시 의도 분류 워크플로우 내부 재진입 구조다. - 재진입 지점은 `IntentGraph.detect_with_workflow()` → LangGraph `classify_intent_node()` → `IntentGraph.detect()` 재호출로 확인된다. - 23 서버가 같은 증상을 재현하지 않은 이유는 임시복구 시 `.env`에서 `INTENT_ENGINE=v1`, `INTENT_USE_LANGGRAPH=false`로 강제 다운그레이드했기 때문이다. ## 2. 근거(코드/문서) ### 2.1 48시간 주요 변경 - `rb8001` `3532600`: `docker-compose.yml` 기본값이 `INTENT_ENGINE=v1` -> `graph`로 변경됨. - `rb8001` `1f39d08`: `/api/message` 응답 후 자기개선 아티팩트 DB 기록 경로 추가(부하 증가 요인). - `robeing-gateway` `5b97bea`: 프록시가 method/body/content-type 전달하도록 변경(이전보다 실제 경로 실행률 상승). - `robeing-monitor` `1221596`: `/health` 호환 엔드포인트 추가(먹통 직접 원인으로 보기 어려움). ### 2.2 재진입(재귀) 경로 - `rb8001/app/services/brain/intent_graph.py:39-41` - `INTENT_USE_LANGGRAPH=true`면 `detect()`가 `detect_with_workflow()`를 호출. - `rb8001/app/services/brain/intent_graph.py:65` - `detect_with_workflow()`가 LangGraph workflow `ainvoke` 실행. - `rb8001/app/services/brain/intent_langgraph_workflow.py:49-60` - `classify_intent_node()` 내부에서 새 `IntentGraph` 생성 후 `intent_graph.detect(message, context)` 재호출. - 위 3개가 결합되어, 워크플로우 내부 분류 노드가 다시 워크플로우 진입 경로를 호출하는 구조가 성립한다. ### 2.3 23/24 차이의 확정 근거 - `DOCS/journey/troubleshooting/260304_51123_임시복구_서비스연속성_조치내역.md:43-49` - 51123 임시복구에서 `INTENT_ENGINE=v1`, `INTENT_USE_LANGGRAPH=false` 적용 기록이 명시됨. - `rb8001/.env:185-188` - 동일 임시복구 성능 오버라이드가 실제 파일에도 존재. - 따라서 같은 코드 베이스라도 23은 재진입 경로를 비활성화했고, 24는 기본 `graph` 경로를 타서 증상이 확대될 수 있다. ### 2.4 외부 근거(공식 문서) - LangGraph는 종료 조건 없이 그래프를 순환 실행하면 `GRAPH_RECURSION_LIMIT` 오류가 발생할 수 있음을 명시한다. - https://docs.langchain.com/oss/python/langgraph/GRAPH_RECURSION_LIMIT - Python은 재귀 깊이에 한계가 있고, 한계를 넘으면 `RecursionError`가 발생한다(재귀 경로가 안전하지 않다는 일반 규칙). - https://docs.python.org/3/library/sys.html#sys.getrecursionlimit - Docker `restart` 정책(`always`/`unless-stopped`)은 프로세스가 비정상 종료될 때 컨테이너를 반복 재기동한다. - https://docs.docker.com/engine/containers/start-containers-automatically/ ## 3. 원인/비원인 구분 | 구분 | 항목 | 판정 | 이유 | |------|------|------|------| | 원인 | 의도 분류 워크플로우 재진입 구조 | 확정 | 코드 호출 체인이 직접 닫힌 루프를 형성 | | 증폭 | `/api/message` 자기개선 DB 기록 추가 | 확정 | 요청당 DB 작업/연결 증가 | | 증폭 | gateway 프록시 body/header 전달 확대 | 확정 | 기존 차단/오류 경로가 실제 실행으로 전환 | | 비원인 | robeing-monitor `/health` 추가 | 제외 | 라우트 1개 추가로 CPU 폭주 구조가 생기지 않음 | | 비원인 | RAM 부족 | 제외 | 기존 관측값 기준 swap/OOM 근거 없음 | ## 4. 해결책(정확/우선순위) ### P0 즉시(운영 보호) 1. 24 서버 배포 전 기본값을 재진입 없는 안전값으로 고정: - `INTENT_ENGINE=v1` - `INTENT_USE_LANGGRAPH=false` 2. 적용 후 30분 관찰 기준: - 컨테이너 `Restarting` 0회 - `python main.py` CPU 지속 고점 급등 재발 없음 ### P1 구조 수정(근본) 1. `IntentGraph.detect()`에 워크플로우 내부 호출 구분 플래그를 추가해 재진입 차단. 2. 또는 `classify_intent_node()`가 `IntentGraph.detect()`가 아닌 전통 분류 함수만 직접 호출하도록 분리. 3. 회귀 테스트 추가: - "workflow classify node가 detect_with_workflow를 다시 호출하지 않는다" 단언 테스트. ### P2 검증 고정(재발 방지) 1. 배포 파이프라인에 스모크 테스트 추가: - `INTENT_ENGINE=graph` 상태에서 단일 메시지 처리 시 호출 깊이/시간 상한 검증. 2. 런타임 가드: - 의도 분류 단계 실행 횟수 카운터를 요청 단위로 기록, 임계 초과 시 즉시 오류/중단. ## 5. 운영 판정 기준 - 완료 판정은 "응답 속도"가 아니라 "먹통 재발 차단"이다. - 아래를 동시에 만족해야 완료: - restart loop 0회 - 운영자가 전체 컨테이너 강제중지 없이 장애 제어 가능 - 09:00~10:00 주요 스케줄 구간에서 CPU 급등/응답 중단 없음