MCP-서버 리팩토링 후 P&ID 추출 테스트전 다른 기능 확인 후 커밋
This commit is contained in:
383
mcp-server/mcp-parallel-diag-coding.md
Normal file
383
mcp-server/mcp-parallel-diag-coding.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# MCP 병렬 아키텍처 진단 후 코딩 작업 계획
|
||||
|
||||
**작성일**: 2026-05-03
|
||||
**기준 문서**: `mcp-parallel-diagnose.md`, `diagnosis-checklist.md`
|
||||
**진단 대상**: `server.py`, `worker/rag_worker.py`, `worker/nl2sql_worker.py`
|
||||
|
||||
---
|
||||
|
||||
## 📋 전체 작업 개요
|
||||
|
||||
진단 결과 총 **50개 항목** 검증 완료:
|
||||
- **HIGH**: 2개 (`asyncio.cache` 누락)
|
||||
- **MED**: 48개 (`asyncio.to_thread` 누락, DB 커넥션 누수)
|
||||
- **LOW**: 0개
|
||||
|
||||
---
|
||||
|
||||
## 🔴 Phase 1: HIGH 우선순위 (즉시 수정)
|
||||
|
||||
### 단위 작업 1: rag_worker.py `asyncio.cache` → `functools.lru_cache` 대체
|
||||
|
||||
**파일**: `mcp-server/worker/rag_worker.py`
|
||||
|
||||
**수정 대상**:
|
||||
- `_get_http_client()` (line 50-52)
|
||||
- `_llm_client()` (line 84-87)
|
||||
|
||||
**수정 내용**:
|
||||
```python
|
||||
# 기존 (Python 3.9+ 전용)
|
||||
@asyncio.cache
|
||||
def _get_http_client():
|
||||
return httpx.AsyncClient(timeout=30)
|
||||
|
||||
# 수정 후 (Python 3.8+ 호환)
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _get_http_client():
|
||||
return httpx.AsyncClient(timeout=30)
|
||||
```
|
||||
|
||||
**검증 방법**:
|
||||
```bash
|
||||
cd mcp-server && python3 -c "import worker.rag_worker; print('OK')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 2: nl2sql_worker.py `asyncio.cache` → `functools.lru_cache` 대체
|
||||
|
||||
**파일**: `mcp-server/worker/nl2sql_worker.py`
|
||||
|
||||
**수정 대상**:
|
||||
- `_llm_client()` (line 54-57)
|
||||
|
||||
**수정 내용**:
|
||||
```python
|
||||
# 기존 (Python 3.9+ 전용)
|
||||
@asyncio.cache
|
||||
def _llm_client():
|
||||
from openai import AsyncOpenAI
|
||||
return AsyncOpenAI(base_url=VLLM_BASE_URL, api_key="dummy")
|
||||
|
||||
# 수정 후 (Python 3.8+ 호환)
|
||||
from functools import lru_cache
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def _llm_client():
|
||||
from openai import AsyncOpenAI
|
||||
return AsyncOpenAI(base_url=VLLM_BASE_URL, api_key="dummy")
|
||||
```
|
||||
|
||||
**검증 방법**:
|
||||
```bash
|
||||
cd mcp-server && python3 -c "import worker.nl2sql_worker; print('OK')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟠 Phase 2: MED 우선순위 (차주 수정)
|
||||
|
||||
### 단위 작업 3: server.py `run_sql()` DB 커넥션 `finally` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 527-541)
|
||||
|
||||
**문제**: 예외 발생 시 커넥션이 닫히지 않을 수 있음
|
||||
|
||||
**수정 내용**:
|
||||
```python
|
||||
# 기존
|
||||
try:
|
||||
conn = _get_db_connection()
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql)
|
||||
rows = cur.fetchall()
|
||||
columns = [desc[0] for desc in cur.description]
|
||||
result_data = [dict(zip(columns, row)) for row in rows]
|
||||
return json.dumps({...}, ensure_ascii=False, default=str)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": f"SQL 실행 실패: {e}"}, ensure_ascii=False)
|
||||
|
||||
# 수정 후
|
||||
conn = None
|
||||
try:
|
||||
conn = _get_db_connection()
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(sql)
|
||||
rows = cur.fetchall()
|
||||
columns = [desc[0] for desc in cur.description]
|
||||
result_data = [dict(zip(columns, row)) for row in rows]
|
||||
return json.dumps({
|
||||
"success": True,
|
||||
"columns": columns,
|
||||
"count": len(result_data),
|
||||
"data": result_data
|
||||
}, ensure_ascii=False, default=str)
|
||||
except Exception as e:
|
||||
return json.dumps({"success": False, "error": f"SQL 실행 실패: {e}"}, ensure_ascii=False)
|
||||
finally:
|
||||
if conn:
|
||||
conn.close()
|
||||
```
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 4: server.py `query_pv_history()` DB 커넥션 `finally` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 557-580)
|
||||
|
||||
**수정 내용**: `finally` 블록 추가로 커넥션 항상 닫도록 보장
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 5: server.py `get_tag_metadata()` DB 커넥션 `finally` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 594-611)
|
||||
|
||||
**수정 내용**: `finally` 블록 추가로 커넥션 항상 닫도록 보장
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 6: server.py `list_drawings()` DB 커넥션 `finally` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 624-639)
|
||||
|
||||
**수정 내용**: `finally` 블록 추가로 커넥션 항상 닫도록 보장
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 7: server.py `extract_pid_tags()` `asyncio.to_thread` 추가 ✅ 완료 (2026-05-03 03:18:20)
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 737-838)
|
||||
|
||||
**수정 내용**:
|
||||
```python
|
||||
# 기존
|
||||
@mcp.tool()
|
||||
def extract_pid_tags(text: str, source_type: str) -> str:
|
||||
...
|
||||
resp = _llm().chat.completions.create(...)
|
||||
...
|
||||
|
||||
# 수정 후
|
||||
@mcp.tool()
|
||||
async def extract_pid_tags(text: str, source_type: str) -> str:
|
||||
...
|
||||
def _call_llm():
|
||||
return _llm().chat.completions.create(...)
|
||||
resp = await asyncio.to_thread(_call_llm)
|
||||
...
|
||||
```
|
||||
|
||||
**검증 방법**: 문법 검증 통과 (`python3 -m py_compile server.py`)
|
||||
|
||||
**백업 위치**: `.rooBackup/2026-05-03-031700/mcp-server/server.py`
|
||||
|
||||
**완료 확인**:
|
||||
- [x] `extract_pid_tags()` → `async def extract_pid_tags()`
|
||||
- [x] `_llm()` 호출을 `asyncio.to_thread`로 감쌈
|
||||
- [x] 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 8: server.py `match_pid_tags()` `asyncio.to_thread` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 825-889)
|
||||
|
||||
**수정 내용**: `async def` + `asyncio.to_thread`로 blocking 함수 오프로드
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 9: server.py `parse_pid_dxf()` `asyncio.to_thread` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 895-992)
|
||||
|
||||
**수정 내용**: `async def` + `asyncio.to_thread`로 blocking 함수 오프로드
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 10: server.py `parse_pid_pdf()` `asyncio.to_thread` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 995-1097)
|
||||
|
||||
**수정 내용**: `async def` + `asyncio.to_thread`로 blocking 함수 오프로드
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 11: server.py `build_pid_graph_parallel()` `asyncio.to_thread` 추가
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 1100-1184)
|
||||
|
||||
**수정 내용**: `async def` + `asyncio.to_thread`로 blocking 함수 오프로드
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 12: server.py `analyze_pid_impact()` `asyncio.to_thread` 추가 ✅ 완료 (2026-05-03 03:29:00)
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 1240-1254)
|
||||
|
||||
**수정 내용**: `def` → `async def`, `PidAnalysisEngine` 호출을 `asyncio.to_thread`로 오프로드
|
||||
|
||||
**검증 방법**: py_compile OK
|
||||
|
||||
**백업 위치**: `.rooBackup/2026-05-03_03-28-00/mcp-server/server.py`
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 13: server.py `parse_pid_drawing()` `asyncio.to_thread` 추가 ✅ 완료 (2026-05-03 03:30:40)
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 1260-1293)
|
||||
|
||||
**수정 내용**: `def` → `async def`, `parse_pid_dxf()`/`parse_pid_pdf()` 호출을 `asyncio.to_thread`로 오프로드
|
||||
|
||||
**검증 방법**: py_compile OK
|
||||
|
||||
**백업 위치**: `.rooBackup/2026-05-03_03-28-00/mcp-server/server.py`
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 14: server.py `query_with_nl()` `asyncio.to_thread` 추가 ✅ 완료 (2026-05-03 03:32:25)
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 658-732)
|
||||
|
||||
**수정 내용**: `def` → `async def`, `_llm()` 호출과 `run_sql()`을 `asyncio.to_thread`로 오프로드
|
||||
|
||||
**검증 방법**: py_compile OK
|
||||
|
||||
**백업 위치**: `.rooBackup/2026-05-03_03-28-00/mcp-server/server.py`
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 15: server.py blocking 헬퍼 함수들 `asyncio.to_thread` 추가 ✅ 완료 (2026-05-03 03:35:15)
|
||||
|
||||
**파일**: `mcp-server/server.py`
|
||||
|
||||
**수정 대상**:
|
||||
- `_embed()` (line 205-213)
|
||||
- `_search()` (line 339-366)
|
||||
- `_get_db_connection()` (line 370-373)
|
||||
- `_llm()` (line 217-220)
|
||||
- `_ocr()` (line 225-245)
|
||||
- `_extract_text_from_dxf()` (line 250-267)
|
||||
- `_extract_text_from_pdf()` (line 270-277)
|
||||
- `_extract_text_from_pdf_ocr()` (line 280-302)
|
||||
- `_convert_dwg_to_dxf_dxflib()` (line 305-334)
|
||||
- `_validate_sql()` (line 376-389)
|
||||
|
||||
**수정 내용**: `async def` + `asyncio.to_thread`로 blocking 함수 오프로드
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
### 단위 작업 16: server.py 타임아웃 300초 → 60초로 줄이기
|
||||
|
||||
**파일**: `mcp-server/server.py` (line 1249)
|
||||
|
||||
**문제**: 타임아웃이 너무 길어 (5분) 장시간 대기 상황 발생 가능
|
||||
|
||||
**수정 내용**:
|
||||
```python
|
||||
# 기존
|
||||
async with httpx.AsyncClient(timeout=300) as client:
|
||||
|
||||
# 수정 후
|
||||
async with httpx.AsyncClient(timeout=60) as client:
|
||||
```
|
||||
|
||||
**검증 방법**: 문법 검증 통과
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Phase 3: LOW 우선순위 (향후 개선)
|
||||
|
||||
### 단위 작업 17: 환경 변수 설정 이동 ✅ 완료 (2026-05-03 03:16:00)
|
||||
|
||||
**파일**: `rag_worker.py`, `nl2sql_worker.py`, `pid_worker.py`
|
||||
|
||||
**수정 내용**: 하드코딩된 URL, 포트, 모델명을 환경 변수로 이동
|
||||
|
||||
**rag_worker.py**:
|
||||
```python
|
||||
OLLAMA_URL = os.environ.get("OLLAMA_URL", "http://localhost:11434")
|
||||
QDRANT_URL = os.environ.get("QDRANT_URL", "http://localhost:6333")
|
||||
VLLM_BASE_URL = os.environ.get("VLLM_BASE_URL", "http://localhost:8000/v1")
|
||||
VLLM_MODEL = os.environ.get("VLLM_MODEL", "Qwen/Qwen3-Coder-Next-FP8")
|
||||
EMBED_MODEL = os.environ.get("EMBED_MODEL", "nomic-embed-text")
|
||||
COL_CODEBASE = os.environ.get("COL_CODEBASE", "ws-65f457145aee80b2")
|
||||
COL_OPC_DOCS = os.environ.get("COL_OPC_DOCS", "experion-opc-docs")
|
||||
```
|
||||
|
||||
**nl2sql_worker.py**:
|
||||
```python
|
||||
DB_CONNECTION_STRING = os.environ.get("DB_CONNECTION_STRING", "postgresql://postgres:postgres@localhost:5432/iiot_platform")
|
||||
DB_TIMEOUT = int(os.environ.get("DB_TIMEOUT", "10"))
|
||||
VLLM_BASE_URL = os.environ.get("VLLM_BASE_URL", "http://localhost:8000/v1")
|
||||
VLLM_MODEL = os.environ.get("VLLM_MODEL", "Qwen/Qwen3-Coder-Next-FP8")
|
||||
```
|
||||
|
||||
**pid_worker.py**:
|
||||
```python
|
||||
VLLM_BASE_URL = os.environ.get("VLLM_BASE_URL", "http://localhost:8000/v1")
|
||||
VLLM_MODEL = os.environ.get("VLLM_MODEL", "Qwen/Qwen3-Coder-Next-FP8")
|
||||
DB_CONNECTION_STRING = os.environ.get("DB_CONNECTION_STRING", "postgresql://postgres:postgres@localhost:5432/iiot_platform")
|
||||
DB_TIMEOUT = int(os.environ.get("DB_TIMEOUT", "10"))
|
||||
```
|
||||
|
||||
**검증 방법**: `python3 -m py_compile` 통과
|
||||
|
||||
**백업 위치**: `.rooBackup/2026-05-03-031500/mcp-server/worker/`
|
||||
|
||||
**완료 확인**:
|
||||
- [x] `rag_worker.py` 문법 검증 통과
|
||||
- [x] `nl2sql_worker.py` 문법 검증 통과
|
||||
- [x] `pid_worker.py` 문법 검증 통과
|
||||
- [x] `mcp-parallel-progress.md`에 완료 기록
|
||||
|
||||
---
|
||||
|
||||
## 📊 작업 순서 요약
|
||||
|
||||
| 단계 | 작업 수 | 우선순위 | 예상 소요 시간 |
|
||||
|------|---------|----------|----------------|
|
||||
| Phase 1 | 2 | HIGH | 10분 |
|
||||
| Phase 2 | 14 | MED | 30분 |
|
||||
| Phase 3 | 1 | LOW | 5분 |
|
||||
| **합계** | **17** | - | **45분** |
|
||||
|
||||
---
|
||||
|
||||
## ✅ 검증 체크리스트
|
||||
|
||||
각 단위 작업 완료 후 다음을 확인:
|
||||
|
||||
- [ ] 문법 검증 통과 (`python3 -c "import ..."` 또는 `python3 -m py_compile`)
|
||||
- [ ] `mcp-parallel-progress.md`에 완료 기록
|
||||
- [ ] 의심가는 점이 있다면 `mcp-parallel-diag-coding.md`에 기록
|
||||
|
||||
---
|
||||
|
||||
## 📝 주의사항
|
||||
|
||||
1. **백업 + Diff**: 기존 파일 수정 전 반드시 `.rooBackup/`에 백업 후 diff 제시
|
||||
2. **Surgical Changes**: 요청된 범위만 수정, 관련 없는 코드 리팩토링 금지
|
||||
3. **Build Validation**: 각 파일 수정 후 문법 검증
|
||||
4. **확신이 가지 않으면 코딩하지 말 것**: 단위 작업 끝에 의심가는 점을 기록
|
||||
Reference in New Issue
Block a user