docs(search): DS-Synthesis-Timeout-Calibration-1 (B-3) closure 보고서

5곳 LLM_TIMEOUT_MS + 2곳 outer wait_for align (classifier 30s 와 동일 정책).
synthesis/evidence/verifier/query_analyzer 모두 동시 부하 시 30s 까지 필요.

Regression fixture 결과: 10/10 HTTP 200 + 5/5 search + 3/3 failure injection
모두 PASS (회귀 0). 응답 시간 +4~20s 증가 (안정성 ↑ 의도된 trade-off).

p95 12s gate 는 여전히 FAIL — B-1 Throughput-1 (priority queue / 모델 분리)
별 plan 으로 latency 단축 방향 진입.
This commit is contained in:
Hyungi Ahn
2026-05-17 08:07:51 +09:00
parent 73f328cb65
commit 7e346d2d3f
@@ -0,0 +1,124 @@
# DS-Synthesis-Timeout-Calibration-1 (B-3) Closure Report
**Date**: 2026-05-17
**Plan 카테고리**: B-3 (PR-Hermes-Polymorphic-Rossum 후속, DS RAG 측 별 트랙)
**범위**: DS RAG 파이프라인 5 stage 의 LLM_TIMEOUT_MS / outer wait_for align (Mac mini 26B serialized concurrent load 대응)
**Commit**: `73f328c`
## Summary
PR-Hermes-Docsrv-Search-1 closure 시점 측정 (`synthesis_ms=30~48s` / `ev_ms=15005` / `query_analyze=45s`) 으로 기존 LLM_TIMEOUT_MS 15s / 3s 가 동시 부하 시 빈발 timeout. classifier (30s) 는 PR-1 closure 에서 이미 align 됐으나 다른 service 들 (synthesis / evidence / verifier / query_analyzer) 은 misaligned 잔존. 본 PR 이 5곳 동시 raise + 2곳 outer wait_for align.
## Root cause (재확인)
Mac mini 26B single-inference + `get_mlx_gate()` Semaphore(1) 직렬화 후 DS RAG 파이프라인 5 stage 가 sequential 누적:
- query_analyzer → evidence + classifier (parallel) → synthesis → verifier
- 각 stage 가 30s 까지 wait 후에야 다음 진행
- 15s LLM_TIMEOUT 시 절반 stage 가 timeout → fallback path 발화 (refusal_gate / verifier skip)
## 변경 사항
5 timeout constants + 2 outer wrappers:
| 파일 | line | 변경 |
|---|---|---|
| `app/services/search/synthesis_service.py` | 43 | `LLM_TIMEOUT_MS 15000 → 30000` |
| `app/services/search/evidence_service.py` | 81 | `LLM_TIMEOUT_MS 15000 → 30000` |
| `app/services/search/verifier_service.py` | 34 | `LLM_TIMEOUT_MS 3000 → 10000` |
| `app/services/search/query_analyzer.py` | 47 | `LLM_TIMEOUT_MS 15000 → 30000` |
| `app/api/search.py` | 522 | `wait_for(classifier_task, 15.0 → 30.0)` |
| `app/api/search.py` | 641 | `wait_for(verifier_task, 4.0 → 10.0)` |
**기존 정합**:
- classifier_service.LLM_TIMEOUT_MS = 30000 (PR-Hermes-Docsrv-Search-1 closure 시 이미 raised)
- ai.classifier.timeout = 30 (config.yaml, 같이 align 완료)
## Regression 검증 (PR-1 Layer 1 fixture re-run, 완료)
**전체 결과 (Pre-B-3 vs Post-B-3)**:
| 항목 | Pre-B-3 | Post-B-3 | 회귀 |
|---|---|---|---|
| docsrv_ask HTTP 200 | 10/10 | **10/10** | ✅ 0 |
| docsrv_search HTTP 200 | 5/5 | **5/5** | ✅ 0 |
| Failure injection 3건 | 3/3 PASS | **3/3 PASS** (401 / 000 / refused) | ✅ 0 |
| classifier ok 비율 | 10/10 | 10/10 | ✅ 0 |
| min | 8.8s | 13.2s | +4.4s |
| p50 | 10.6s | **23.2s** | +12.6s |
| mean | 15.0s | **25.8s** | +10.8s |
| p95 | 34.8s | **50.7s** | +15.9s (ASME outlier) |
| max | 34.8s | **50.7s** | +15.9s |
**Per-query 비교**:
| Query | Pre-B-3 | Post-B-3 | Δ |
|---|---|---|---|
| ask-a1-memo-hit | 9.3s | 13.2s | +3.9s |
| ask-a2-voice | 13.0s | 19.9s | +6.9s |
| ask-a3-bridge | 10.1s | 22.0s | +11.9s |
| ask-b1-asme | 30.7s | **50.7s** | **+20.0s** (이전 timeout 부근, 끝까지 완료) |
| ask-b2-drift | 11.5s | 30.5s | +19.0s |
| ask-b3-digest | 10.4s | 25.6s | +15.2s |
| ask-c1-today | 34.8s | 30.5s | -4.3s |
| ask-c2-decision | 10.6s | 23.2s | +12.6s |
| ask-d1-secret | 10.5s | 20.7s | +10.2s |
| ask-d2-noexist | 8.8s | 21.8s | +13.0s |
**해석**:
- 응답 시간 +4~20s 증가 — 의도된 trade-off (timeout fallback path 대신 끝까지 completion)
- classifier ok 정상 path 100% (conservative_refuse 회피)
- functional 변화 0 (verdict, citations, sources, refused 모두 동일 패턴)
- p95 12s gate 는 여전히 FAIL (이전부터 ASME outlier 로 fail, B-3 가 안정성 우선이라 latency 더 증가 — B-1 Throughput-1 진입 시 단축 검토)
## 결정 사항
1. **B-3 = SHIPPED**:
- 5곳 LLM_TIMEOUT + 2곳 outer wait_for align 완료
- Mac mini 26B concurrent saturation 대응 완성 (classifier 외 4 service)
- functional 회귀 0, latency +4~20s (안정성 ↑ 의도된 trade-off)
2. **B-1 (Throughput-1) = 별 plan 으로 분리**:
- 본 B-3 가 latency 증가 방향 — B-1 이 latency 단축 방향 (priority queue / 모델 분리)
- architecture 변경이라 별 plan + 사용자 검토 필수
- 후보: (a) `asyncio.PriorityQueue` (user ask priority 0 / background priority 100) (b) Mac mini 4B 모델 추가 로드 (triage 만 분리) (c) GPU Ollama 4B 재도입 (PR #20 reverse)
3. **B-2 (Classifier-Threshold-Tune-1) = 1주 query 로그 대기**:
- 사용자 실제 query 패턴 + rerank score 분포 측정 후 `CONSERVATIVE_WEAK=0.35` recalibration
- 진입 = 2026-05-24 (1주 baseline) 이후
## File changes
- `app/services/search/synthesis_service.py` (line 43)
- `app/services/search/evidence_service.py` (line 81)
- `app/services/search/verifier_service.py` (line 34)
- `app/services/search/query_analyzer.py` (line 47)
- `app/api/search.py` (line 522, 641)
총 5 file, 6 timeout constants/wrappers 변경.
## 7일 안전망 (2026-05-24)
- git revert `73f328c` (단일 commit 으로 묶임)
- 별 백업 파일 0 (git history 가 안전망)
## 검증 commands (재실행)
```bash
# 회귀 fixture
ssh macmini "bash ~/.hermes/fixtures/pr_search1_layer1.sh"
# DS 로그 (classifier ok 비율)
ssh gpu "cd ~/Documents/code/hyungi_Document_Server && \
docker compose logs --since=5m fastapi | grep -E 'classifier (ok|error|timeout)' | tail -10"
# Hermes E2E (Curl-Refine-2 확인 + 회귀 0)
ssh macmini "HERMES_DOCSRV_TOKEN=... && hermes chat -s docsrv_ask -q '내 자료에서 voice memo 찾아줘'"
```
## 후속 트랙
| 우선 | 트랙 | 진입 |
|---|---|---|
| **P2** | DS-Mac-mini-26B-Throughput-1 (B-1) | architecture 변경 plan 필요 — priority queue / 모델 분리 비교 |
| **대기** | DS-Classifier-Threshold-Tune-1 (B-2) | 2026-05-24 1주 query 로그 baseline 후 |
| (선택) | LLM_TIMEOUT_MS centralization | 5 service 가 각자 상수 보유 — 공통 config 항목 (`ai.llm_timeout_default: 30`) 도입 검토. P3 |