# Storage Layout — Document Server

**버전**: 2026-05-11 (Storage PR-1 read-only inventory 결과 반영)
**역할 분리 원칙**: `feedback_knowledge_in_db_blob_in_nas.md` — 지식·검색 대상 = DB, binary·source = NAS.
**Plan 정합**: `~/.claude/plans/beszel-tingly-sloth.md` v6 의 Storage Backbone 트랙.

## 전제

- DB 본체 (Postgres) = GPU 서버 잔류 (CPU/RAM 우위, 결정 라운드 9).
- NAS = bulk binary / source / 변경 적은 자료 backbone.
- AI 호출은 GPU/맥미니로 라우팅 (Document Server 와 별 채널).

## 머신 / 마운트 토폴로지 (2026-05-11 실측)

| 머신 | 역할 | IP | 디스크 |
|---|---|---|---|
| GPU 서버 (RTX 4070 Ti Super, Ubuntu) | Docker 호스트 — fastapi / postgres / worker / Caddy / frontend | LAN 192.168.1.186 / Tailscale 100.111.160.84 | rootfs 936G / 182G used / 715G free |
| DS1525+ (Synology) | NFS export + Hyper Backup + DR Tier 1 | LAN 192.168.1.227 / Tailscale 100.101.79.37 | `/volume4` 3.5TB / 401GB used / 3.1TB free |
| Mac mini M4 Pro | MLX inference (Document Server 외부) | LAN 192.168.1.x / Tailscale 100.76.254.116 | (Document Server storage 영역 0) |

GPU 서버의 NFS mount (`/proc/mounts` 실측):

```
192.168.1.227:/volume4/Document_Server  /mnt/nas/Document_Server
  nfs4 rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,
  soft,proto=tcp,timeo=10,retrans=3,_netdev
```

**의미**:
- 이미 **soft mount + timeo=10 + retrans=3** 적용 중 → plan v6 Storage PR-2 의 "실측 후 mount 전략 결정" 의 *현 baseline 이 곧 권고안*. 변경 필요 X.
- vers=4.1, NFSv4 (NFSv3 아님 → grace period / lock 처리 OK).
- LAN 직결 (Tailscale 경유 아님) — D10 (DS1525+ Tailscale TCP 차단) 영향 없음.

## NAS volume 안 Document Server layout

`/mnt/nas/Document_Server/` (= NAS `/volume4/Document_Server/`):

| 경로 | 용도 | 크기 | 변경 빈도 | 분류 |
|---|---|---|---|---|
| `PKM/` | 사용자 컨텐츠 원본 (knowledge + 입력 inbox + references) | **38GB** | 일 단위 | 이미 NAS ✓ |
| `PKM/Knowledge/` | 정리된 지식 자료 (압력용기, ASME, 가스, 등) | 31GB | 일 단위 | 이미 NAS ✓ |
| `PKM/References/` | 레퍼런스 PDF | 5.5GB | 주 단위 | 이미 NAS ✓ |
| `PKM/Inbox/` | 업로드 진입 (drag-drop / file_watcher) | 1.5GB | 시간 단위 | 이미 NAS ✓ |
| `PKM/.preview/` | LibreOffice PDF 변환 cache | (소량, 6 entry) | 신규 업로드 시 | 이미 NAS ✓ (검토: GPU 로 이동 가치 작음) |
| `PKM/Recordings/` | 음성 녹음 | 20KB | 거의 X | 이미 NAS ✓ |
| `PKM/Videos/` | 비디오 | 116KB | 거의 X | 이미 NAS ✓ |
| `PKM/Archive/` | 아카이브 | 0 | — | 이미 NAS ✓ |
| `Main/` | 별 디렉토리 (의미 확인 필요) | 14GB | ? | 이미 NAS ✓ |
| `extracted_images/` | Markdown Phase 1B.5 추출 이미지 (`document_images`) | 1.2MB | 변환 시 | 이미 NAS ✓ |
| `study_question_images/` | 가스기사 문제 첨부 이미지 (PR-8) | 12MB | 회차 업로드 시 | 이미 NAS ✓ |
| `프로그래밍 전 참고 자료/` | (소량) | 56KB | 거의 X | 이미 NAS ✓ |

**결론**: NAS 의 binary/source layer 는 **이미 잘 구성되어 있음**. 추가 이관 후보 (Storage PR-3/4 candidate) 는 사실상 없음 — 단, **PR-4 MailPlus ingest** 도입 시 `extracted_emails/` 신규 디렉토리 추가 예정.

## GPU 서버 local storage (Postgres + worker 모델 + 로그)

| 경로 / Volume | 용도 | 크기 | 분류 |
|---|---|---|---|
| Postgres data dir (`pgdata` named volume) | DB 본체 | **1.1GB** | GPU 잔류 (plan 결정) |
| `hyungi_document_server_ollama_data` | Ollama 모델 weight (embedding / 분류 / 임베딩 등) | **11.9GB** | GPU 잔류 (GPU 인접 필수) |
| `hyungi_document_server_stt_models` | STT (faster-whisper 등) 모델 | 3.0GB | GPU 잔류 |
| `hyungi_document_server_reranker_cache` | bge-reranker-v2-m3 cache | 2.1GB | GPU 잔류 |
| `hyungi_document_server_ocr_models` | OCR 모델 | 1.4GB | GPU 잔류 |
| `hyungi_document_server_marker_models` | marker-pdf cache | 4KB (외부 HF cache 사용 추정) | GPU 잔류 |
| `hyungi_document_server_caddy_data` | Caddy ACME / log | (작음) | GPU 잔류 |
| `~/Documents/code/hyungi_Document_Server/logs/` | fastapi 로그 (rotation) | 21MB | GPU 잔류 |

**합계**: AI 모델 캐시 ~18.5GB + Postgres 1.1GB + 로그/Caddy < 100MB ≈ GPU local 20GB 사용 중.

## 컨테이너 마운트 매트릭스 (`docker-compose.yml` 발췌)

| 컨테이너 | 마운트 | 모드 | 비고 |
|---|---|---|---|
| postgres | `pgdata:/var/lib/postgresql/data` + `./migrations:/docker-entrypoint-initdb.d` | rw | DB 본체 named volume |
| kordoc-service | `${NAS}/Document_Server:/documents` | **ro** | PDF/HWP parse |
| ocr-service | `${NAS}/Document_Server:/documents` + `ocr_models:/root/.cache` | **ro** + rw | |
| marker-service | `${NAS}/Document_Server:/documents` + `marker_models:/models` | **ro** + rw | PDF→markdown |
| stt-service | `${NAS}/Document_Server:/documents` + `stt_models:/root/.cache` | **ro** + rw | |
| ai-gateway / ollama | `ollama_data:/root/.ollama` | rw | GPU 인접 |
| reranker | `reranker_cache:/data` | rw | |
| fastapi | `${NAS}/Document_Server:/documents` + `./config.yaml ./domain_policy.yaml ./scripts ./logs ./migrations` | **rw** + ro/rw | NAS 쓰기 권한은 fastapi 만 |
| caddy | `./Caddyfile + caddy_data:/data` | rw | |

**관찰**:
- worker 컨테이너 (kordoc/ocr/marker/stt) 는 모두 NAS **read-only** 마운트 → 원본 안전.
- fastapi 만 NAS **rw** → 업로드/preview/extracted_images 쓰기 단일 책임.
- `./migrations` 이 postgres 의 `docker-entrypoint-initdb.d` 와 fastapi 의 `/app/migrations` 양쪽에 마운트. 단 실제 migration runner 는 fastapi `init_db()` 만 사용 (postgres init scripts 는 첫 생성 시만 실행 → 효과 X, 안전).

## 정책 정리

### NAS 에 두는 것 (binary / source / 변경 적음)
- 사용자 컨텐츠 원본 (`PKM/*`)
- 변환 cache (`PKM/.preview/`, `extracted_images/`)
- 학습 자료 첨부 (`study_question_images/`)
- 향후: `extracted_emails/` (PR-4)

### GPU 서버 local 에 두는 것 (latency 민감 / hot path / 큰 caching)
- Postgres data dir (DB 본체)
- HNSW index, pgvector index (Postgres 내부)
- AI 모델 weight cache (embedding / OCR / STT / reranker / marker / ollama)
- worker 임시 캐시 (각 컨테이너 named volume)
- 로그 (`./logs`)

### 명확하게 어디에 두지 않음
- 검색 인덱스: Postgres 안 (pgvector + pg_trgm) — NAS X
- chunk text: Postgres 안 (`document_chunks.text_content`) — NAS X
- 메모 본문: Postgres 안 (`documents.extracted_text`) — NAS X
- AI 분류 결과: Postgres 안 (`documents.ai_domain/ai_tags`) — NAS X

## NFS mount 옵션 권고 (Storage PR-2 baseline)

현 GPU 서버 mount = 이미 권고안과 같음:

```
192.168.1.227:/volume4/Document_Server /mnt/nas/Document_Server  nfs4
  rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,
  soft,proto=tcp,timeo=10,retrans=3,_netdev
```

**근거**:
- `soft` — NAS down 시 process 영구 hang 회피 (plan v6 Storage PR-2 핵심 요구). 단 쓰기 도중 부분 실패 위험 → fastapi 의 `upload` / `extract` / `marker` worker 가 fsync + rename(atomic) + DB row 작성 ordering 필수 (코드 검토는 PR-2 영역).
- `timeo=10/retrans=3` — 1초 단위 timeout × 3회. 30초 안 NAS 응답 없으면 EIO. 운영 1년+ 안정 (별 incident 없음).
- `vers=4.1` — NFSv3 의 lock daemon 불필요, idempotent.
- `_netdev` — boot 시 network 이후 마운트.

### Storage PR-2 시 점검 항목 (read-only PR-1 scope 외)
- 한글 NFC/NFD 정규화 wrapper (`feedback_nfs_korean_path_normalize.md`) — 기존 `document_images` 경로 처리 코드 일반화
- uid/gid 매핑 (Synology user ↔ Docker container user)
- NAS 장애 시뮬레이션 gate (운영 mount 아닌 isolated test mount 부터)
- 쓰기 경로 fsync + rename(atomic) + 실패 명시 처리

## 백업 정책

- NAS = Synology Hyper Backup (DR Tier 1, 별 트랙 `project_db_backup.md` 참조)
- Postgres = pkm DB → Tier 0 GPU + Tier 1 DS1525+ Vol3 + Tier 2 C2 Hyper Backup (별 트랙)
- AI 모델 캐시 = 백업 불필요 (재다운로드 가능)

## 다음 단계

- Storage PR-2 (NFS 표준화 + 정규화 wrapper + 장애 시뮬 gate) — 운영 변경 동반 → 별 PR
- Storage PR-3/4 (원본 이관) — **현 상태 이미 NAS, 추가 작업 0**
- Storage PR-5 (events `raw_metadata` 첨부 = extracted_emails) — events PR-4 (MailPlus ingest) 와 합류
