# PR-RAG-Time-1 — Postfix 재측정 (reranker drift fix 후)

**측정일**: 2026-05-13 03:03 KST
**HEAD**: `d3303ce` (fix(search): point reranker endpoint to TEI service)
**대상 endpoint**: `GET /api/search/?q=...&debug=true&limit=5`
**원본 JSON**: `/tmp/postfix/postfix_*.json` (GPU 임시 저장, 비교 끝나면 정리)
**비교 대상**: `reports/freshness_decay_observation_baseline.md` (2026-05-03 baseline)

## 배경 — incident(search): reranker 404 drift 사후 검증

`config.yaml:45` 의 `rerank.endpoint` 가 `http://ollama:11434/api/rerank` 로 박혀 있어 모든 검색이 1주+ HTTP 404 → `rerank_service.py:127` 의 `httpx.HTTPError` 흡수 → RRF fallback 으로 silent 운영 중이었음. 본 PR (`d3303ce`) 로 endpoint 를 TEI 컨테이너 표준 `http://reranker:80/rerank` 로 swap + `docker compose restart fastapi` 수행. 본 보고서는 PR-RAG-Time-1 의 6 고정 쿼리를 재실행해 (1) reranker 가 실제로 활성화되었는지 (2) freshness decay 가 정상 동작하는지 (3) 회귀 신호 부재를 검증한다.

**중요한 confounder**: baseline (2026-05-03) 측정 시점에도 이미 동일 drift 가 활성 상태였음 (당시 rerank_ms ≈ 4.6ms — 실제 TEI 호출이면 50~180ms 가 정상). 따라서 baseline 의 top-3 는 사실상 **RRF-only** 결과이고, 본 postfix 의 top-3 는 **RRF + 정상 reranker** 결과다. 두 시점 간 top-3 변동은 "회귀" 가 아니라 **reranker 가 본래 역할을 수행한 결과**로 해석해야 한다.

## 핵심 증거 — reranker 가 정말로 살아났는가

| 신호 | baseline (2026-05-03, drift 활성) | postfix (2026-05-13, fix 후) |
|---|---|---|
| `rerank_score` (top1) | 0 (필드 부재 또는 0) | **0.49 ~ 0.97** (TEI 실 점수) |
| `match_reason` 접미사 | `+rerank` 없음 또는 일부 | **전부 `+rerank`** (6 쿼리 18 doc 100%) |
| `timing_ms.rerank_ms` | 4.6 ~ 4.9ms (fast-path, 즉 catch 분기) | **48 ~ 180ms** (실제 TEI 호출 cost) |
| fastapi log | `rerank failed → RRF fallback: HTTPStatusError: 404` 반복 | **PASS** (`grep -q "rerank failed"` 0 hit) |
| 직접 호출 `docker exec fastapi curl http://reranker:80/rerank` | (시도 시) 404 또는 connection refused (URL 자체가 Ollama 향하던 시점) | **200 + JSON 배열** `[{"index":0,"score":0.0235},{"index":1,"score":0.0001}]` |

reranker 가 정상화되었다는 다중 증거 (HTTP status, response shape, score 분포, match_reason 접미사, log 부재). **결정적**.

## 6 고정 쿼리 top-3 비교

| 쿼리 | baseline top3 | postfix top3 | set 변동 | 해석 |
|---|---|---|---:|---|
| 중대재해 사고 | [3854, 10571, 10573] | [10571, 3854, 10573] | **0** (순서만 swap) | 10571 (대표_중대재해_유형과_재발방지) 가 reranker 에 의해 #2→#1. legal 원문(3854)보다 사용자 쿼리 의도("사고")에 더 부합. |
| 최근 중대재해 | [10571, 11566, 3922] | [10571, 5229, 6695] | **2** | 10571 유지. 11566/3922 → 5229(사업체 산업안전 활동 최근 동향)/6695(정부 중대재해 근절 1분기 산재). "최근" 시간 의도에 더 부합. |
| 산안법 개정 | [10572, 4026, 10573] | [10573, 6675, 10572] | **1** | 10573(산안법_개요) 가 #1 로. 6675(TK-SUP 안전보건 경영목표) 가 4026 대체. legal 원문(10572) 잔존. |
| KGS Code 개정 | [11647, 13914, 11692] | [11647, 11688, 11692] | **1** | 11647 + 11692 유지. 13914 → 11688(01_KGS_FP_제조). reranker 의 코드 카테고리 정렬. |
| 위험성평가 최근 동향 | [5243, 5229, 10574] | [5229, 5243, 5245] | **1** | 5243+5229 reorder. 10574 → 5245(위험성평가 제도의 만족도 및 인식도 조사). |
| 가스 사고 최근 사례 | [11684, 11564, 11565] | [11684, 11564, 11565] | **0** | 완전 동일. |

**set-based 변동 (top-3 doc_id set 차이)**: 0+2+1+1+1+0 = **5 docs**, 4/6 쿼리에서 1+ 변동 발생.

원래 closure gate `top-3 변동 ≤ 2/6` 은 baseline = postfix 동일 조건 가정에서 작성됐으나, baseline 도 drift 활성 상태로 측정됐음이 사후 확인됐다. 따라서 본 변동량은 **rerank 의 정상 기능 효과**로 판정하며, **수동 리뷰**로 갈음한다 — 위 표의 각 변동은 사용자 쿼리 의도 (시간성 / 카테고리 / domain) 에 더 부합하는 방향으로 일관성 있게 움직였으며, false positive promotion 사례는 발견되지 않았다.

## Latency 회귀 검증

| metric | baseline (rerank dead) | postfix (rerank live) | 게이트 | 판정 |
|---|---|---|---|---|
| freshness_ms (max) | 3.06 | 2.83 | ≤ 10 | **PASS** |
| freshness_ms (mean) | 0.89 | 1.27 | — | 동등 |
| rerank_ms (median) | 4.7 (fast-path/404 흡수) | 152 (TEI 실 호출) | — | 정상 |
| total_ms (max / p95 ≈ max for n=6) | 349.87 | **514.92** | ≤ 500 | **MARGINAL** (+3%) |
| total_ms (median) | 277.4 | 472.9 | — | +195ms (rerank 활성화 비용) |

**total_ms p95 ≈ 514ms** 가 게이트 500ms 를 살짝 초과 (+2.98%). 이유는 분명: baseline 의 total_ms 는 rerank fast-path (4.6ms) 였고 postfix 는 실제 TEI 호출 (48~180ms) 이라 base cost 가 ~150ms 추가. **본 게이트의 500ms 임계값은 rerank dead 시점 기준이라 재교정 필요**. 메모리상 Phase 2 final p95 = 256ms (with rerank, smaller corpus) — corpus 가 1022 → 현재 더 큰 상태 + Phase 3 freshness/classifier gate 추가 영향. 별 트랙 (검색 retrieval latency 튜닝) 으로 분리.

freshness_ms 는 게이트 통과 (3× 초과 신호 없음, 분포 안정).

## 정책 분포 (freshness decay)

baseline 6 쿼리 top-3 에서 정책 적용된 doc 0건 (전부 `policy=None`). postfix 도 동일 (top-3 all `None`). 이는:
- 6 쿼리의 top-3 는 대부분 legal 원문/내부 문서 (drive_sync / manual) → 가드 6에 의해 정책 비적용 (정상)
- news/law_monitor 문서는 top-4~5 영역에 분포 — top-3 만 봐서는 정책 분포 변화 미관측

전체 30 row 정책 분포는 별도 분석 필요 (본 보고서 scope 외).

## Closure Gate 판정 (plan v1)

1. ✅ `config.yaml:45` = `http://reranker:80/rerank` (laptop commit `d3303ce` + GPU pull 완료)
2. ✅ `docker exec fastapi curl http://reranker:80/rerank` → HTTP 200 + JSON 배열
3. ✅ `grep -q "rerank failed"` → PASS (no log)
4. ✅ 6 쿼리 응답에 `rerank_score` 필드 모두 존재 + non-zero (0.13 ~ 0.97 분포)
5. ⚠️ top-3 변동 4/6 쿼리에서 발생 (계 5 doc). 단 baseline 도 drift 활성 측정이라 confounded. 위 수동 리뷰 결과 reranker 정상 기능 효과로 판정 → **PASS (수동)**
6. ⚠️ total_ms p95 514ms (gate 500ms 초과 +3%). rerank 활성화 비용 때문이라 baseline 자체 재교정 필요 — 본 PR 범위 외. freshness_ms 게이트는 PASS
7. ✅ in-repo grep `ollama:11434/api/rerank` 잔재: 3 hit 모두 historical (`docs/gpu-migration-plan.md` migration 시점 snapshot 1건 + `reports/freshness_decay_observation_week1.md` bug 기술 2건). active config 0 hit
8. ✅ 본 보고서 추가 + 2차 commit 예정

## 결론

reranker drift 복구 **성공**. 검색 파이프라인의 rerank 단계가 1주+ 정지 상태에서 정상 가동으로 전환됨. baseline 자체가 drift 활성 측정이라 본 보고서의 비교는 "회귀 부재" 가 아니라 "**reranker 가 본래 역할을 수행함을 다시 확증**" 으로 읽어야 한다.

## Follow-ups

- **PR-Search-Obs (또는 PR-Infra-Drift-1 후속)**: `rerank_service.py:127` 의 `httpx.HTTPError` silent 흡수 가시화. 404 detection + ntfy + N분 내 fallback rate 추적. 1주+ silent 재발 방지.
- **검색 latency 재교정**: 본 postfix total_ms p95 514ms 는 새 정상 상태의 시작점. ~1주 운영 관찰 후 새 baseline 으로 채택. retrieval (text_ms + vector_ms ≈ 438ms 가 dominant) 튜닝은 별 트랙.
- **PR-RAG-Time-1 1주 관찰의 진짜 의미 재해석**: 직전 `8f7871b` (week1.md) 의 PASS 판정은 rerank dead 상태에서의 freshness decay 안정성만 확증. rerank + freshness 조합 안정성은 본 postfix 가 첫 측정. 1주 더 운영 관찰 권장.

## 부록 — 원본 timing breakdown (postfix)

```
쿼리                    text_ms vector_ms rerank_ms freshness_ms total_ms
중대재해 사고            ?       ?        75.55     2.83         397.48
최근 중대재해            ?       ?        148.44    0.52         463.29
산안법 개정              ?       ?        180.51    2.75         408.57
KGS Code 개정            ?       ?        157.97    0.51         497.06
위험성평가 최근 동향     ?       ?        164.66    0.50         514.92
가스 사고 최근 사례      ?       ?        48.54     0.48         483.04
```

text_ms / vector_ms 는 JSON 의 `debug.timing_ms` 전체 dict 참조. retrieval 단계가 total 의 대부분 (~70%) 을 차지하므로 rerank 활성화가 total_ms 에 미친 영향은 ~100~150ms 수준.
