# S1 데이터·백엔드 트랙 적용 runbook (plan ds-s1-backend-1)

> 코드는 `feat/s1-dedup-fields` 브랜치에 완성. 이 문서는 **prod(GPU) 적용 게이트** 절차.
> ⚠ 적용은 사용자 명시 go 필요 — 본 runbook 은 자동 실행되지 않는다.

## 0. 사전 조건 (게이트)

- [ ] **검색실험 Soft Lock 확인** — `~/.claude/.search-experiment-active` 부재여야 함.
  현재(2026-06-05) 부재 = 비활성. migration 317 은 startup 자동적용 → `docker compose up`
  이 restart 를 유발하므로, 실험 활성 시엔 예외창 합의 후에만.
- [ ] **불가침 면 (검색실험 유효성)**: embedding 모델 / 벡터 인덱스(ivfflat/partial) /
  retrieval config / config.yaml 의 ai·model 섹션 **미접촉**. 본 트랙 변경면은
  dedup 컬럼 + office_md + storage scaffold(env) 뿐.

## 1. migration 번호

- 317(dedup 3컬럼) **단일** 클레임. P0-4=(C) 무변경이라 신규 migration 미추가.
- S2/S3 트랙이 같은 317 을 발행하지 않도록 조율(startup 카오스 방지).

## 2. restart 셋 (한 번에 배치)

| 서비스 | 변경 | 재시작 사유 |
|---|---|---|
| `fastapi` | A(317 dedup) + B(dedup API) + D(storage scaffold) | startup migration 자동적용 + 코드 |
| `marker_worker`(fastapi 내 스케줄러) | C(office_md 분기) + **markitdown 신규 pip dep** | rebuild 필요 |

> markitdown 은 신규 의존성 → `docker compose build` 필수(force-recreate 만으론 image 미갱신,
> feedback_docker_compose_build_vs_force_recreate). office 변환(OOXML)에만 필요.

## 3. 적용 순서 (inventory → config → deploy → verify)

```bash
ssh gpu && cd ~/Documents/code/hyungi_Document_Server

# (1) pre-A-1 안전망 — DB 덤프 (repo 밖)
bash scripts/s1_pre_change_backup.sh pre-a1

# (2) 코드 가져오기 + 빌드(markitdown dep 반영) + 적용
git fetch && git checkout feat/s1-dedup-fields   # 또는 main 머지 후 main
docker compose build fastapi                      # markitdown 설치 (requirements 에 추가 필요)
docker compose up -d fastapi                       # startup 에서 migration 317 자동적용

# (3) migration 317 적용 확인
docker compose exec -T postgres psql -U pkm -d pkm -c \
  "SELECT version,name FROM schema_migrations WHERE version=317;"
docker compose exec -T postgres psql -U pkm -d pkm -c \
  "\d documents" | grep -E 'original_filename|duplicate_of|duplicate_count'
```

> **requirements**: office OOXML 변환에 `markitdown` 추가 필요(`requirements.txt`/pyproject).
> markdownify·LibreOffice 는 기존. 빌드 전 dep 추가 PR 필수(없으면 OOXML 변환이 OfficeMdError→failed,
> hwp/PDF/passthrough 는 정상).

## 4. backfill (코드 적용·검증 후, 야간 비중첩창)

> dedup 컬럼 정합은 **야간 잡 `dedup_reconcile`(03:30 KST, main.py)** 이 매일 멱등 재계산한다
> (soft-delete 잔여 드리프트 자동 정리). 아래 `backfill_dedup.py` 수동 실행은 적용 직후 1회
> 초기 채움/즉시 확인용 — 이후엔 야간 잡이 유지.

```bash
# (4a) dedup backfill (초기 1회) — 먼저 dry-run 으로 정확한 UPDATE set 확인
bash scripts/s1_pre_change_backup.sh pre-b4
docker compose exec fastapi python /app/scripts/backfill_dedup.py --dry-run
docker compose exec fastapi python /app/scripts/backfill_dedup.py --apply

# (4b) office/hwp pending markdown 백필 — C-2 라이브 ingestion 과 비중첩 야간창
docker compose exec fastapi python /app/scripts/backfill_nonpdf_markdown.py --dry-run
docker compose exec fastapi python /app/scripts/backfill_nonpdf_markdown.py --apply --limit 20   # sample 먼저
docker compose exec fastapi python /app/scripts/backfill_nonpdf_markdown.py --apply              # 전체
```

## 5. verify (smoke)

```bash
# /duplicates shape
curl -s -H "Authorization: Bearer $TOK" https://document.hyungi.net/api/documents/duplicates | jq '{total_groups,total_duplicate_docs, g0:.groups[0]}'
# office 변환 결과 (sample doc)
docker compose exec -T postgres psql -U pkm -d pkm -c \
  "SELECT md_status,md_extraction_engine,length(md_content) FROM documents WHERE id=<office_doc_id>;"
# md_status success→completed 직렬화 (앱 계약)
curl -s -H "Authorization: Bearer $TOK" https://document.hyungi.net/api/documents/<id> | jq '.md_status'
```

## 6. 롤백

- 컬럼만 빠른 롤백: `scripts/rollback_317.sql` (수동, schema_migrations 317 행도 삭제).
- 전체 복원: `scripts/s1_pre_change_backup.sh` 가 출력한 `.sql.gz` → psql 복원.
