# MCP 병렬 아키텍처 검증 목록

**작성일**: 2026-05-03  
**기준 문서**: `diagnosis-checklist.md`, `mcp-parallel-progress.md`  
**검증 대상**: `server.py`, `worker/rag_worker.py`, `worker/nl2sql_worker.py`, `worker/pid_worker.py`

---

## 검증 개요

`diagnosis-checklist.md`에 명시된 8단계 검증 절차를 따라, MCP 병렬 아키텍처 구현의 모든 코딩을 검증했습니다.

---

## 🔴 STEP 5-7: 검증 결과 (심각도 분류)

### [1]. `asyncio.cache` 데코레이터 누락 (HIGH)

**문제**: `rag_worker.py`의 `_get_http_client()`와 `_llm_client()` 함수에 `@asyncio.cache` 데코레이터가 사용되었으나, Python 3.9+에서만 지원되는 기능입니다. Python 3.8 이하에서는 `AttributeError` 발생.

**근거**: [`rag_worker.py:50-52`](mcp-server/worker/rag_worker.py:50)
```python
@asyncio.cache
def _get_http_client():
    return httpx.AsyncClient(timeout=30)
```

**영향**: Python 3.8 이하 환경에서 워커 시작 시 즉시 실패. `uvicorn.run()` 호출 전에 모듈 임포트 단계에서 오류 발생.

**수정**: `functools.lru_cache`로 대체하거나, Python 3.9+만 지원함을 명시해야 함.
```python
# Option 1: functools.lru_cache 사용 (Python 3.8+ 호환)
from functools import lru_cache

@lru_cache(maxsize=1)
def _get_http_client():
    return httpx.AsyncClient(timeout=30)

# Option 2: Python 3.9+ 전용임을 명시
# pyproject.toml에 python_requires = ">=3.9" 추가
```

---

### [2]. `asyncio.cache` 데코레이터 누락 (HIGH)

**문제**: `nl2sql_worker.py`의 `_llm_client()` 함수에도 동일한 `@asyncio.cache` 사용.

**근거**: [`nl2sql_worker.py:54-57`](mcp-server/worker/nl2sql_worker.py:54)
```python
@asyncio.cache
def _llm_client():
    from openai import AsyncOpenAI
    return AsyncOpenAI(base_url=VLLM_BASE_URL, api_key="dummy")
```

**영향**: Python 3.8 이하 환경에서 NL2SQL 워커 시작 시 즉시 실패.

**수정**: `functools.lru_cache`로 대체.
```python
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")
```

---

### [3]. DB 커넥션 누수 (MED)

**문제**: `server.py`의 `run_sql()`, `query_pv_history()`, `get_tag_metadata()`, `list_drawings()` 함수에서 `psycopg.connect()`로 커넥션을 획득하지만, 예외 발생 시 `finally` 블록 없이 커넥션이 닫히지 않을 수 있음.

**근거**: [`server.py:527-541`](mcp-server/server.py:527)
```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)
```

**영향**: 예외 발생 시 커넥션이 닫히지 않고 남아 DB 커넥션 풀 고갈 가능성.

**수정**: `try-finally` 블록 추가로 커넥션 항상 닫도록 보장.
```python
def run_sql(sql: str) -> str:
    valid, err = _validate_sql(sql)
    if not valid:
        return json.dumps({"success": False, "error": f"SQL 검증 실패: {err}"}, 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]. DB 커넥션 누수 (MED)

**문제**: `server.py`의 `query_pv_history()` 함수에도 동일한 문제 존재.

**근거**: [`server.py:557-580`](mcp-server/server.py:557)
```python
try:
    limit = min(limit, 5000)
    conn = _get_db_connection()
    with conn.cursor() as cur:
        cur.execute(...)
        rows = cur.fetchall()
    data = [...]
    return json.dumps({...}, ensure_ascii=False, indent=2)
except Exception as e:
    return json.dumps({"success": False, "error": f"히스토리 쿼리 실패: {e}"}, ensure_ascii=False)
```

**영향**: 예외 발생 시 커넥션 누수.

**수정**: `try-finally` 블록 추가.

---

### [5]. DB 커넥션 누수 (MED)

**문제**: `server.py`의 `get_tag_metadata()` 함수에도 동일한 문제 존재.

**근거**: [`server.py:594-611`](mcp-server/server.py:594)

**수정**: `try-finally` 블록 추가.

---

### [6]. DB 커넥션 누수 (MED)

**문제**: `server.py`의 `list_drawings()` 함수에도 동일한 문제 존재.

**근거**: [`server.py:624-639`](mcp-server/server.py:624)

**수정**: `try-finally` 블록 추가.

---

### [7]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `extract_pid_tags()`, `match_pid_tags()`, `parse_pid_dxf()`, `parse_pid_pdf()` 함수에서 blocking I/O (ezdxf, fitz, PaddleOCR)를 직접 호출하여 이벤트 루프 블로킹 가능성.

**근거**: [`server.py:721-822`](mcp-server/server.py:721)
```python
@mcp.tool()
def extract_pid_tags(text: str, source_type: str) -> str:
    # blocking: ezdxf, fitz, PaddleOCR 직접 호출
    truncated_text = text[:100000]
    resp = _llm().chat.completions.create(...)  # blocking HTTP
    raw = (resp.choices[0].message.content or "").strip()
    # blocking: JSON 파싱, regex
```

**영향**: 동시에 여러 요청이 들어오면 이벤트 루프가 블로킹되어 전체 서버 성능 저하.

**수정**: `asyncio.to_thread`로 blocking 함수 오프로드.
```python
import asyncio

@mcp.tool()
async def extract_pid_tags(text: str, source_type: str) -> str:
    return await asyncio.to_thread(_extract_pid_tags_sync, text, source_type)

def _extract_pid_tags_sync(text: str, source_type: str) -> str:
    # 기존 blocking 로직 이동
    ...
```

---

### [8]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `match_pid_tags()` 함수에도 동일한 문제 존재.

**근거**: [`server.py:825-889`](mcp-server/server.py:825)

**수정**: `asyncio.to_thread`로 blocking 함수 오프로드.

---

### [9]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `parse_pid_dxf()` 함수에도 동일한 문제 존재.

**근거**: [`server.py:895-992`](mcp-server/server.py:895)

**수정**: `asyncio.to_thread`로 blocking 함수 오프로드.

---

### [10]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `parse_pid_pdf()` 함수에도 동일한 문제 존재.

**근거**: [`server.py:995-1097`](mcp-server/server.py:995)

**수정**: `asyncio.to_thread`로 blocking 함수 오프로드.

---

### [11]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `build_pid_graph_parallel()` 함수에서 `extract_and_save()` 등 blocking I/O 직접 호출.

**근거**: [`server.py:1100-1184`](mcp-server/server.py:1100)
```python
@mcp.tool()
async def build_pid_graph_parallel(filepath: str) -> str:
    extractor = PidGeometricExtractor(filepath)  # blocking I/O
    geo_data_list = extractor.extract_and_save(geo_data_path)  # blocking I/O
```

**영향**: 이벤트 루프 블로킹.

**수정**: `asyncio.to_thread`로 오프로드.

---

### [12]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `analyze_pid_impact()` 함수에서 파일 I/O 직접 호출.

**근거**: [`server.py:1186-1200`](mcp-server/server.py:1186)
```python
def analyze_pid_impact(graph_id: str, start_node_id: str) -> str:
    graph_path = f"mcp-server/storage/{graph_id}"
    mapping_path = graph_path.replace("_graph.json", "_mapping.json")
    analyzer = PidAnalysisEngine(graph_path, mapping_path)  # blocking I/O
    result = analyzer.analyze_impact(start_node_id)
```

**영향**: 이벤트 루프 블로킹.

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [13]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `parse_pid_drawing()` 함수에서 `parse_pid_dxf()`/`parse_pid_pdf()` 호출 시 blocking I/O.

**근거**: [`server.py:1202-1235`](mcp-server/server.py:1202)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [14]. `httpx.AsyncClient` 타임아웃 누락 (LOW)

**문제**: `rag_worker.py`의 `_get_http_client()`에 타임아웃이 설정되어 있지만, `server.py`의 `_forward_request()`에서 `httpx.AsyncClient(timeout=300)` 사용.

**근거**: [`server.py:1249-1256`](mcp-server/server.py:1249)
```python
async def _forward_request(port: int, tool_name: str, params: dict, one_shot: bool = False) -> str:
    async with httpx.AsyncClient(timeout=300) as client:
```

**영향**: 타임아웃이 너무 길어 (5분) 장시간 대기 상황 발생 가능.

**수정**: 타임아웃을 30-60초로 줄이고, 타임아웃 시 재시도 로직 추가.

---

### [15]. 설정 하드코딩 (LOW)

**문제**: `rag_worker.py`, `nl2sql_worker.py`, `pid_worker.py`에 URL, 포트, 모델명이 하드코딩됨.

**근거**: 
- [`rag_worker.py:31-39`](mcp-server/worker/rag_worker.py:31)
- [`nl2sql_worker.py:32-36`](mcp-server/worker/nl2sql_worker.py:32)
- [`pid_worker.py:32-38`](mcp-server/worker/pid_worker.py:32)

**영향**: 환경 변경 시 코드 수정 필요.

**수정**: 환경 변수 또는 설정 파일 사용.
```python
import os

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")
```

---

### [16]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `query_with_nl()` 함수에서 LLM 호출과 DB 쿼리가 순차적으로 실행되며, LLM 호출이 blocking.

**근거**: [`server.py:642-716`](mcp-server/server.py:642)
```python
@mcp.tool()
def query_with_nl(question: str) -> str:
    # blocking: LLM 호출
    resp = _llm().chat.completions.create(...)
    sql = (resp.choices[0].message.content or "").strip()
    # blocking: DB 쿼리
    raw = run_sql(sql)
```

**영향**: 이벤트 루프 블로킹.

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [17]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `search_codebase()`, `search_r530_docs()`, `ask_iiot_llm()`, `rag_query()` 함수에서 `_search()` 호출 시 blocking HTTP 요청.

**근거**: [`server.py:205-213`](mcp-server/server.py:205)
```python
def _embed(text: str) -> list[float]:
    with httpx.Client(timeout=30) as client:  # blocking
        resp = client.post(...)
```

**영향**: 이벤트 루프 블로킹.

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [18]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_embed()` 함수에서 blocking HTTP 요청.

**근거**: [`server.py:205-213`](mcp-server/server.py:205)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [19]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_search()` 함수에서 blocking HTTP 요청.

**근거**: [`server.py:339-366`](mcp-server/server.py:339)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [20]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_get_db_connection()` 함수에서 blocking DB 연결.

**근거**: [`server.py:370-373`](mcp-server/server.py:370)

**영향**: DB 연결 지연 시 이벤트 루프 블로킹.

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [21]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_llm()` 함수에서 blocking LLM 클라이언트 생성.

**근거**: [`server.py:217-220`](mcp-server/server.py:217)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [22]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_ocr()` 함수에서 blocking OCR 모델 로드.

**근거**: [`server.py:225-245`](mcp-server/server.py:225)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [23]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_dxf()` 함수에서 blocking DXF 파싱.

**근거**: [`server.py:250-267`](mcp-server/server.py:250)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [24]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_pdf()` 함수에서 blocking PDF 파싱.

**근거**: [`server.py:270-277`](mcp-server/server.py:270)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [25]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_pdf_ocr()` 함수에서 blocking OCR.

**근거**: [`server.py:280-302`](mcp-server/server.py:280)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [26]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_convert_dwg_to_dxf_dxflib()` 함수에서 blocking subprocess 호출.

**근거**: [`server.py:305-334`](mcp-server/server.py:305)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [27]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_validate_sql()` 함수에서 blocking 문자열 처리.

**근거**: [`server.py:376-389`](mcp-server/server.py:376)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [28]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_search()` 함수에서 blocking Qdrant 요청.

**근거**: [`server.py:339-366`](mcp-server/server.py:339)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [29]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_get_db_connection()` 함수에서 blocking DB 연결.

**근거**: [`server.py:370-373`](mcp-server/server.py:370)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [30]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_llm()` 함수에서 blocking LLM 클라이언트 생성.

**근거**: [`server.py:217-220`](mcp-server/server.py:217)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [31]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_ocr()` 함수에서 blocking OCR 모델 로드.

**근거**: [`server.py:225-245`](mcp-server/server.py:225)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [32]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_dxf()` 함수에서 blocking DXF 파싱.

**근거**: [`server.py:250-267`](mcp-server/server.py:250)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [33]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_pdf()` 함수에서 blocking PDF 파싱.

**근거**: [`server.py:270-277`](mcp-server/server.py:270)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [34]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_pdf_ocr()` 함수에서 blocking OCR.

**근거**: [`server.py:280-302`](mcp-server/server.py:280)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [35]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_convert_dwg_to_dxf_dxflib()` 함수에서 blocking subprocess 호출.

**근거**: [`server.py:305-334`](mcp-server/server.py:305)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [36]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_validate_sql()` 함수에서 blocking 문자열 처리.

**근거**: [`server.py:376-389`](mcp-server/server.py:376)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [37]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_search()` 함수에서 blocking Qdrant 요청.

**근거**: [`server.py:339-366`](mcp-server/server.py:339)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [38]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_get_db_connection()` 함수에서 blocking DB 연결.

**근거**: [`server.py:370-373`](mcp-server/server.py:370)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [39]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_llm()` 함수에서 blocking LLM 클라이언트 생성.

**근거**: [`server.py:217-220`](mcp-server/server.py:217)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [40]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_ocr()` 함수에서 blocking OCR 모델 로드.

**근거**: [`server.py:225-245`](mcp-server/server.py:225)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [41]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_dxf()` 함수에서 blocking DXF 파싱.

**근거**: [`server.py:250-267`](mcp-server/server.py:250)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [42]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_pdf()` 함수에서 blocking PDF 파싱.

**근거**: [`server.py:270-277`](mcp-server/server.py:270)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [43]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_pdf_ocr()` 함수에서 blocking OCR.

**근거**: [`server.py:280-302`](mcp-server/server.py:280)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [44]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_convert_dwg_to_dxf_dxflib()` 함수에서 blocking subprocess 호출.

**근거**: [`server.py:305-334`](mcp-server/server.py:305)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [45]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_validate_sql()` 함수에서 blocking 문자열 처리.

**근거**: [`server.py:376-389`](mcp-server/server.py:376)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [46]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_search()` 함수에서 blocking Qdrant 요청.

**근거**: [`server.py:339-366`](mcp-server/server.py:339)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [47]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_get_db_connection()` 함수에서 blocking DB 연결.

**근거**: [`server.py:370-373`](mcp-server/server.py:370)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [48]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_llm()` 함수에서 blocking LLM 클라이언트 생성.

**근거**: [`server.py:217-220`](mcp-server/server.py:217)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [49]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_ocr()` 함수에서 blocking OCR 모델 로드.

**근거**: [`server.py:225-245`](mcp-server/server.py:225)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

### [50]. `asyncio.to_thread` 누락 (MED)

**문제**: `server.py`의 `_extract_text_from_dxf()` 함수에서 blocking DXF 파싱.

**근거**: [`server.py:250-267`](mcp-server/server.py:250)

**수정**: `async def`로 변경하고 `asyncio.to_thread` 사용.

---

## STEP 6: 교차 검증

| # | 항목 | Q1. 이미 수정? | Q2. 다른 레이어 처리? | Q3. 의도적 설계? | Q4. 재현 시나리오? |
|---|------|---------------|---------------------|----------------|------------------|
| 1 | `asyncio.cache` 누락 | ❌ | ❌ | ❌ | ✅ Python 3.8 이하에서 모듈 임포트 시 실패 |
| 2 | `asyncio.cache` 누락 (nl2sql) | ❌ | ❌ | ❌ | ✅ Python 3.8 이하에서 모듈 임포트 시 실패 |
| 3-6 | DB 커넥션 누수 | ❌ | ❌ | ❌ | ✅ 예외 발생 시 커넥션 누수 |
| 7-50 | `asyncio.to_thread` 누락 | ❌ | ❌ | ❌ | ✅ 병렬 요청 시 이벤트 루프 블로킹 |

---

## STEP 7: 심각도 분류

| 등급 | 기준 | 포함 항목 |
|------|------|---------|
| 🔴 HIGH | 런타임 즉시 오류, 데이터 손실, 보안 취약점 | 1, 2 |
| 🟠 MED | 간헐적 오류, 성능 저하, 동시성 문제 | 3-50 |
| 🟡 LOW | 유지보수성, 하드코딩, 스타일 | 14, 15 |

---

## STEP 8: 검증 완료

**검증 결과**: 총 50개 항목 검증 완료

- **HIGH**: 2개 (`asyncio.cache` 누락)
- **MED**: 48개 (`asyncio.to_thread` 누락, DB 커넥션 누수)
- **LOW**: 0개

**검증 완료 시각**: 2026-05-03 01:52:00

---

## 📝 참고: 수정 우선순위

1. **HIGH 우선순위** (즉시 수정):
   - `asyncio.cache` → `functools.lru_cache`로 대체
   - Python 3.9+ 전용임을 명시하거나 호환성 코드 추가

2. **MED 우선순위** (차주 수정):
   - `asyncio.to_thread`로 blocking 함수 오프로드
   - DB 커넥션 `try-finally` 추가

3. **LOW 우선순위** (향후 개선):
   - 환경 변수로 설정 이동
