Files
ExperionCrawler/mcp-server/mcp-parallel-diag-coding.md

11 KiB

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.cachefunctools.lru_cache 대체

파일: mcp-server/worker/rag_worker.py

수정 대상:

  • _get_http_client() (line 50-52)
  • _llm_client() (line 84-87)

수정 내용:

# 기존 (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)

검증 방법:

cd mcp-server && python3 -c "import worker.rag_worker; print('OK')"

단위 작업 2: nl2sql_worker.py asyncio.cachefunctools.lru_cache 대체

파일: mcp-server/worker/nl2sql_worker.py

수정 대상:

  • _llm_client() (line 54-57)

수정 내용:

# 기존 (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")

검증 방법:

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)

문제: 예외 발생 시 커넥션이 닫히지 않을 수 있음

수정 내용:

# 기존
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)

수정 내용:

# 기존
@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

완료 확인:

  • extract_pid_tags()async def extract_pid_tags()
  • _llm() 호출을 asyncio.to_thread로 감쌈
  • 문법 검증 통과

단위 작업 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)

수정 내용: defasync 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)

수정 내용: defasync 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)

수정 내용: defasync 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분) 장시간 대기 상황 발생 가능

수정 내용:

# 기존
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:

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:

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:

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/

완료 확인:

  • rag_worker.py 문법 검증 통과
  • nl2sql_worker.py 문법 검증 통과
  • pid_worker.py 문법 검증 통과
  • 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. 확신이 가지 않으면 코딩하지 말 것: 단위 작업 끝에 의심가는 점을 기록