# 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 급등/응답 중단 없음
