chore: bump version 0.32.0 → 0.33.0

28.4k 문서 도그푸딩이 드러낸 다섯 결함(#228–#232)을 전부 처리한 릴리스.
다섯 건 모두 단위 테스트가 잡지 못하는 종류였다 — 규모에서만 드러나는 성능
붕괴, 조용히 사라지는 내용, 멈춘 것처럼 보이는 진행 표시.

minor 트리거는 두 가지다. #232 의 parser_version cascade
(pdf-text-v1 → pdf-text-v2, 기존 색인 PDF 재처리 유발)와 신규 config 키
([ingest.pdf.ocr] render_library / render_dpi). 나머지 셋은 additive wire
변경 + 관측성 개선이라 자체로는 patch 급이다.

  #232  스캔 PDF 를 페이지 렌더링으로 OCR — CCITTFax/JBIG2/Flate/JPX 가
        조용히 버려지던 것을 pdfium 런타임 로드로 커버. 없으면 DCTDecode
        폴백 + 왜 건너뛰었는지 명시. 단일 바이너리 유지.
        한국어 CCITT 스캔 CER 12.6~15.7% (이 릴리스 이전엔 본문 0자).
  #229  chunks_fts 삭제를 rowid 조회로 (V016) — 문서 200건 삭제
        1590초 → 2.0초. 검색 결과 불변, 재색인 불필요.
  #230  Lance fragment 압축 + 삭제 배치화 — 2.8만 문서 색인 속도가 분당
        32~36건으로 평탄 유지(이전 30.7 → 4.3 단조 하락), 15 GB → 2.8 GB.
  #228  삭제 sweep 을 진행바·ndjson 로그에 노출 — 무표시 구간이 hang 으로
        오인되던 것. sweep 이 취소 플래그도 보게 했다.
  #231  derivation cache 배칭 + 계측 — 가설은 재현되지 않았다(히트 139초
        vs 우회 1180초). 이슈 스스로 예측한 대로 #229 가 해소했다.

도그푸딩 evidence 는 tasks/HOTFIXES.md 2026-08-16 / 2026-08-17 항목.
사용자 영향 설명은 docs/release-notes/v0.33.0-draft.md.

곁들여, `cargo clippy --workspace --all-targets -- -D warnings` 가 main 에서
실패하고 있던 것을 고쳤다(툴체인 상승으로 새 lint 둘이 기존 코드에 걸림).
방치하면 새 위반이 그 그늘에 숨는다는 것을 실제로 겪었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017c9JwQq8ZkGvYjpKXMiDhF
This commit is contained in:
2026-08-17 06:23:20 +09:00
parent 500ed96358
commit 5596d41565
5 changed files with 144 additions and 22 deletions

View File

@@ -4,6 +4,7 @@
| 버전 | 요약 | 노트 | | 버전 | 요약 | 노트 |
|---|---|---| |---|---|---|
| **v0.33.0** | 28.4k 문서 도그푸딩이 드러낸 다섯 결함(#228#232) — 스캔 PDF 페이지 렌더링(CCITT/JBIG2/Flate/JPX 커버), chunks_fts 삭제 800배, Lance 압축, sweep 진행 표시, 임베딩 캐시 계측. | [release](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.33.0) · [notes](docs/release-notes/v0.33.0-draft.md) |
| **v0.32.0** | ponytail-audit over-engineering 정리 arc — 죽은 search-cache scaffold 제거, 9 AST chunker→1 통합(byte-identical), shim crate 흡수(crate 22→20), FusionPolicy/YAML/NLI. | [release](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.32.0) · [notes](docs/release-notes/v0.32.0-draft.md) | | **v0.32.0** | ponytail-audit over-engineering 정리 arc — 죽은 search-cache scaffold 제거, 9 AST chunker→1 통합(byte-identical), shim crate 흡수(crate 22→20), FusionPolicy/YAML/NLI. | [release](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.32.0) · [notes](docs/release-notes/v0.32.0-draft.md) |
| **v0.31.0** | 척추 단순화 + 캐시 전면화 — tui·multi-turn 세션·candle 제거, config v4→v5, image/pdf/code 임베딩 캐시, OCR/caption derivation 캐시. | [release](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.31.0) · [notes](docs/release-notes/v0.31.0-draft.md) | | **v0.31.0** | 척추 단순화 + 캐시 전면화 — tui·multi-turn 세션·candle 제거, config v4→v5, image/pdf/code 임베딩 캐시, OCR/caption derivation 캐시. | [release](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.31.0) · [notes](docs/release-notes/v0.31.0-draft.md) |
| **v0.30.1** | pdf-page-v1.2 oversize 분할 / rag-v4 provenance 라벨 / config budget floor 검증. | [release](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.30.1) · [notes](docs/release-notes/v0.30.1-draft.md) | | **v0.30.1** | pdf-page-v1.2 oversize 분할 / rag-v4 provenance 라벨 / config budget floor 검증. | [release](https://gitea.altair823.xyz/altair823-org/kebab/releases/tag/v0.30.1) · [notes](docs/release-notes/v0.30.1-draft.md) |

40
Cargo.lock generated
View File

@@ -4256,7 +4256,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-app" name = "kebab-app"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64 0.22.1", "base64 0.22.1",
@@ -4300,7 +4300,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-chunk" name = "kebab-chunk"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
@@ -4318,7 +4318,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-cli" name = "kebab-cli"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap", "clap",
@@ -4338,7 +4338,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-config" name = "kebab-config"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"dirs 5.0.1", "dirs 5.0.1",
@@ -4354,7 +4354,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-core" name = "kebab-core"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
@@ -4369,7 +4369,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-embed-local" name = "kebab-embed-local"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"fastembed", "fastembed",
@@ -4382,7 +4382,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-embed-ollama" name = "kebab-embed-ollama"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kebab-config", "kebab-config",
@@ -4397,7 +4397,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-eval" name = "kebab-eval"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kebab-app", "kebab-app",
@@ -4416,7 +4416,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-llm-local" name = "kebab-llm-local"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kebab-config", "kebab-config",
@@ -4432,7 +4432,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-mcp" name = "kebab-mcp"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kebab-app", "kebab-app",
@@ -4450,7 +4450,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-nli" name = "kebab-nli"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"hf-hub", "hf-hub",
@@ -4465,7 +4465,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-parse-code" name = "kebab-parse-code"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"gix", "gix",
@@ -4488,7 +4488,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-parse-image" name = "kebab-parse-image"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"ab_glyph", "ab_glyph",
"anyhow", "anyhow",
@@ -4515,7 +4515,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-parse-md" name = "kebab-parse-md"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"kebab-core", "kebab-core",
@@ -4532,7 +4532,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-parse-pdf" name = "kebab-parse-pdf"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
@@ -4549,7 +4549,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-rag" name = "kebab-rag"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
@@ -4570,7 +4570,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-search" name = "kebab-search"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"globset", "globset",
@@ -4588,7 +4588,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-source-fs" name = "kebab-source-fs"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
@@ -4606,7 +4606,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-store-sqlite" name = "kebab-store-sqlite"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"blake3", "blake3",
@@ -4626,7 +4626,7 @@ dependencies = [
[[package]] [[package]]
name = "kebab-store-vector" name = "kebab-store-vector"
version = "0.32.0" version = "0.33.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"arrow", "arrow",

View File

@@ -28,7 +28,7 @@ edition = "2024"
rust-version = "1.85" rust-version = "1.85"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
repository = "https://github.com/altair823/kebab" repository = "https://github.com/altair823/kebab"
version = "0.32.0" # v0.32.0 — ponytail-audit over-engineering 정리 arc (능력 불변, 표면·구조만 단순화). (1) 죽은 search-cache scaffold 제거(#219): #214 LRU 캐시 제거 후 잔존 facade/`search --no-cache`·`--explain` 플래그/`explain_default` config 정리, search() byte-identical. (2) 9개 동일 code AST chunker → CodeAstV1Chunker 통합(#220, 3030줄): chunker_version 라벨 verbatim 유지 → chunk_id byte-identical(pre/post-arc 실코퍼스 A/B 120==120 diff-empty 확증), 재인덱싱 0. (3) 빈 re-export shim kebab-embed/kebab-llm → kebab-core mock feature 흡수(#221): crate 22→20, 동작 불변. (4) FusionPolicy 1-arm enum inline + dual-YAML(serde_yaml→serde_yaml_ng 일원화, 중복 serde_yaml dep 제거; unsafe-libyaml 은 ng 가 여전히 가져옴) + NLI default 제거(#222). minor 트리거 = #219 의 `--no-cache`/`search --explain` 플래그 + `explain_default` config 키 제거(인터페이스 변경). 도그푸딩: HOTFIXES 2026-06-27(chunker A/B byte-identical + GPU r9700 arctic ingest). 상세 docs/release-notes/v0.32.0-draft.md — CLAUDE.md §Release version = "0.33.0" # v0.33.0 — 28.4k 문서 도그푸딩이 드러낸 다섯 결함(#228#232). minor 트리거는 #232 의 parser_version cascade(pdf-text-v1 → v2, 기존 색인 PDF 재처리 유발) + 신규 config 키([ingest.pdf.ocr] render_library / render_dpi). (1) 스캔 PDF 를 페이지 렌더링으로 OCR(#232): CCITTFax/JBIG2/Flate/JPX 가 조용히 버려지던 것을 pdfium 런타임 로드로 커버, 없으면 DCTDecode 폴백 + 이유 명시(ocr_skipped_pages / failure_reason / doctor pdf_render). 단일 바이너리 유지(ldd 에 pdfium 없음). (2) chunks_fts 삭제를 rowid 조회로(#229, V016): 문서 200건 삭제 1590초 → 2.0초, 검색 결과 불변. (3) Lance fragment 압축 + 삭제 배치화(#230): 2.8만 문서 색인 속도 분당 32~36건 평탄 유지(이전 30.7 → 4.3 단조 하락), 15 GB → 2.8 GB. (4) 삭제 sweep 을 진행바·ndjson 에 노출(#228): 무표시 구간이 hang 으로 오인되던 것 + sweep 취소 협조. (5) derivation cache 배칭·계측(#231): 가설은 재현 안 됨(히트 139초 vs 우회 1180초), asset_timings 에 cache_hit/miss/ms 노출. 도그푸딩: HOTFIXES 2026-08-16/17(CER 12.6~15.7% 한국어 CCITT 스캔, 이전 본문 0자). 상세 docs/release-notes/v0.33.0-draft.md — CLAUDE.md §Release
# pre-v0.18 workspace-wide cleanup: enable clippy::pedantic group with # pre-v0.18 workspace-wide cleanup: enable clippy::pedantic group with
# intentional allow-list. The allowed lints are either cosmetic (doc style), # intentional allow-list. The allowed lints are either cosmetic (doc style),

View File

@@ -4,7 +4,7 @@
## 한 줄 요약 ## 한 줄 요약
현재 **v0.32.0**. P0P7 + P9(CLI/MCP) + P10 머지 완료, P8(audio)·P9-5(desktop)만 보류. `kebab ingest` 가 markdown · 이미지(OCR+caption) · PDF(텍스트+page citation+scanned OCR) · 소스코드(Rust/Python/TS/JS/Go/Java/Kotlin/C/C++ AST + Tier2 리소스 + Tier3 fallback) 를 단일 binary 로 색인. `kebab search`(lexical/vector/hybrid RRF) / `kebab ask`(RAG + 근거 인용 + NLI groundedness + multi-hop) 가 매체 가로질러 동작. UI 는 CLI + MCP stdio server(TUI 는 v0.31.0 에서 제거). 현재 **v0.33.0**. P0P7 + P9(CLI/MCP) + P10 머지 완료, P8(audio)·P9-5(desktop)만 보류. `kebab ingest` 가 markdown · 이미지(OCR+caption) · PDF(텍스트+page citation+scanned OCR) · 소스코드(Rust/Python/TS/JS/Go/Java/Kotlin/C/C++ AST + Tier2 리소스 + Tier3 fallback) 를 단일 binary 로 색인. `kebab search`(lexical/vector/hybrid RRF) / `kebab ask`(RAG + 근거 인용 + NLI groundedness + multi-hop) 가 매체 가로질러 동작. UI 는 CLI + MCP stdio server(TUI 는 v0.31.0 에서 제거).
## Phase 로드맵 ## Phase 로드맵
@@ -28,6 +28,7 @@
버전별 변경은 [CHANGELOG.md](CHANGELOG.md), 머지 후 deviation 의 dated 로그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md)(동작이 설계와 다를 때의 진실)에 있다. 굵직한 흐름만: 버전별 변경은 [CHANGELOG.md](CHANGELOG.md), 머지 후 deviation 의 dated 로그는 [tasks/HOTFIXES.md](tasks/HOTFIXES.md)(동작이 설계와 다를 때의 진실)에 있다. 굵직한 흐름만:
- **v0.33.0** — 28.4k 문서 도그푸딩이 드러낸 다섯 결함(#228#232). 스캔 PDF 를 페이지 렌더링으로 읽어 CCITT/JBIG2/Flate/JPX 커버(pdfium 런타임 로드, 없으면 DCTDecode 폴백 + 이유 명시), `chunks_fts` 삭제 800배, Lance 압축, sweep 진행 표시, 임베딩 캐시 계측.
- **v0.32.0** — ponytail-audit over-engineering 정리 (죽은 scaffold 제거, 9 AST chunker→1, shim crate 흡수 22→20). - **v0.32.0** — ponytail-audit over-engineering 정리 (죽은 scaffold 제거, 9 AST chunker→1, shim crate 흡수 22→20).
- **v0.31.0** — 척추 단순화(TUI·세션·candle 제거, config v5) + 임베딩/OCR/caption 캐시 전면화. - **v0.31.0** — 척추 단순화(TUI·세션·candle 제거, config v5) + 임베딩/OCR/caption 캐시 전면화.
- **v0.270.30** — paddle-onnx 네이티브 OCR · provenance 출처 필터 · md-heading-v2 oversize 분할. - **v0.270.30** — paddle-onnx 네이티브 OCR · provenance 출처 필터 · md-heading-v2 oversize 분할.
@@ -39,6 +40,8 @@
- **P8 audio** — whisper-rs 시스템 dep vs 외부 transcription endpoint 결정 필요. 사용자 패턴상 보류. - **P8 audio** — whisper-rs 시스템 dep vs 외부 transcription endpoint 결정 필요. 사용자 패턴상 보류.
- 그 외는 도그푸딩 follow-up — 발견 시 HOTFIXES 에 dated entry + 필요 시 ARCHITECTURE 갱신. - 그 외는 도그푸딩 follow-up — 발견 시 HOTFIXES 에 dated entry + 필요 시 ARCHITECTURE 갱신.
v0.33.0 도그푸딩에서 나왔으나 아직 이슈로 등록하지 않은 것 셋: `kebab reset``storage.data_dir` 을 무시하고 XDG 기본 경로를 지운다, 자동생성 파일 탐지가 코드 파일이 아니라 모든 파일에 적용된다, 기본 설정에 환각 방어가 없다(`nli_threshold` 기본값 꺼짐).
## 검증 ## 검증
릴리스 binary 의 종단 동작은 [docs/SMOKE.md](docs/SMOKE.md)(격리 KB 1세션 검증) + [docs/DOGFOOD.md](docs/DOGFOOD.md)(기능별 시나리오). 실 데이터 도그푸딩 evidence 는 HOTFIXES dated entry. 릴리스 binary 의 종단 동작은 [docs/SMOKE.md](docs/SMOKE.md)(격리 KB 1세션 검증) + [docs/DOGFOOD.md](docs/DOGFOOD.md)(기능별 시나리오). 실 데이터 도그푸딩 evidence 는 HOTFIXES dated entry.

View File

@@ -0,0 +1,118 @@
# v0.33.0 — 28.4k 문서 도그푸딩이 드러낸 다섯 결함
v0.32.0 이후 실제 코퍼스(나무위키 18,282 + Apache Jira 10,146 = 28,427 문서 / 600,808 청크)로 도그푸딩하면서 등록한 이슈 #228~#232 를 전부 처리한 릴리스. 다섯 건 모두 **단위 테스트가 잡지 못하는 종류**였다 — 규모에서만 드러나는 성능 붕괴, 조용히 사라지는 내용, 멈춘 것처럼 보이는 진행 표시.
`minor` bump 트리거는 두 가지다. #232`parser_version` cascade(`pdf-text-v1``pdf-text-v2`, 기존 색인 PDF 재처리 유발)와 신규 config 키(`[ingest.pdf.ocr] render_library` / `render_dpi`). 나머지 셋은 additive wire 변경 + 관측성 개선이라 자체로는 patch 급이다.
실측 evidence 는 전부 `tasks/HOTFIXES.md` 의 2026-08-16 / 2026-08-17 dated entry 에 있다.
---
## 1. 스캔 PDF 가 인코딩에 따라 통째로 버려지던 문제 (#232) — minor
**변경 사실.** 스캔 페이지를 래스터화하는 방식이 "페이지에 박힌 JPEG 을 그대로 꺼내기" 에서 "페이지를 렌더링하기" 로 바뀌었다. 예전에는 `/Filter`**정확히 DCTDecode** 인 이미지 하나뿐인 페이지만 OCR 됐다. 실제 스캔본에서 흔한 CCITTFaxDecode·JBIG2Decode·FlateDecode·JPXDecode, 필터 체인, 그리고 "배경 + 마스크" 로 나뉜 구조는 전부 걸러졌다.
걸러진 페이지는 **조용히** 사라졌다. 색인은 "성공" 으로 끝나고, 검색이 안 되는 시점에야 알게 되며, 그때 원인이 PDF 인코더라는 걸 역추적할 방법이 없었다.
**trade-off.** 렌더러(pdfium)는 공유 라이브러리로만 배포되고 정적 빌드가 없다. 바이너리에 넣으면 kebab 의 단일 실행 파일 성질이 깨지므로 **런타임에 찾아 쓰는 방식**으로 붙였다. 즉 스캔본 커버리지가 환경에 따라 달라진다 — 라이브러리가 있으면 전부, 없으면 예전과 같이 DCTDecode 페이지만 처리한다.
**mitigation.** 없을 때 조용히 넘어가지 않는다.
- 페이지마다 이유를 찍는다: `⊘ OCR page 5 건너뜀 — 래스터 없음 — 이 페이지의 인코딩은 페이지 렌더러 없이 읽을 수 없다`
- run 요약에 건수가 나온다: `scanned 1 new 1 … ocr-skipped 1`
- `--json``ingest_report.ocr_skipped_pages``pdf_ocr_finished.failure_reason` (`no_renderer` / `render_error` / `unopenable_pdf` / `ocr_error`)
- `kebab doctor``pdf_render` 가 지금 어느 쪽인지 보고한다
**upgrade 절차.** 스캔 PDF 를 쓰지 않으면 할 일이 없다. 쓴다면:
1. `libpdfium` 을 로더가 찾는 곳에 두거나 `[ingest.pdf.ocr] render_library = "/경로/libpdfium.so"` 로 지정한다. 공개 빌드는 [bblanchon/pdfium-binaries](https://github.com/bblanchon/pdfium-binaries) 에 있고 리눅스 x64 기준 7.4 MB 다.
2. `kebab doctor``pdf_render` 가 경로를 보고하는지 확인한다.
3. **재색인은 자동이다.** `parser_version``pdf-text-v2` 로 올라가서 다음 `kebab ingest` 가 기존 PDF 를 다시 처리한다 — `--force-reingest` 를 떠올릴 필요가 없다.
**해상도 노브.** `render_dpi` (기본 300) 가 렌더 해상도 **요청**이고, `max_pixels` 가 실제 상한이다. PDF 기본값 `max_pixels = 2048` 이면 A4 는 175 DPI 언저리에서 잘린다. 더 선명하게 읽히고 싶으면 `max_pixels` 를 올려야 하는데(A4 300 DPI 는 약 3500), 그만큼 큰 이미지를 OCR 엔진이 받는다. 엔진 자체도 256~4096 으로 다시 조인다.
**도그푸딩.** 정답 텍스트를 아는 한국어 스캔 합성 픽스처(CCITT 3건, 22쪽)를 config 기본 모델 `qwen2.5vl:3b` 로 읽혔다.
| 문서 | 페이지 | 문자 오류율 |
|---|---|---|
| 브렌다(메이즈 러너 시리즈) | 8 | 15.65% |
| 비행대 스토리 | 8 | 12.55% |
| 구 아놀리엔 | 6 | 15.08% |
22쪽 전부 OCR 성공, 건너뜀 0. **이 릴리스 이전에는 세 문서 모두 본문이 0자**였으므로 비교 대상 오류율은 100% 다.
**엔진 선택 주의.** 범용 멀티모달 모델은 OCR 엔진이 아니다. 같은 픽스처를 `gemma3:4b` 로 읽혔더니 래스터는 정상인데 출력이 원문과 무관한 환각이었고("이 문서에 스무어라가 포함되어 있습니다"), 해상도가 올라가자 밀집 한국어 페이지에서 180초 타임아웃까지 났다. config 기본값이 `qwen2.5vl:3b` 인 이유가 이것이다.
---
## 2. 어휘 인덱스 삭제가 코퍼스 전체 스캔이던 문제 (#229) — patch (동작 불변)
**변경 사실.** `chunks_fts` 의 삭제 트리거가 행을 찾는 기준이 `chunk_id` 에서 `rowid` 로 바뀌었다 (마이그레이션 V016, 자동).
`chunk_id` 는 FTS5 에서 색인되지 않는 컬럼이라 조건을 만족할 색인이 없었고, 청크 한 건 삭제가 인덱스 전체를 훑었다. 문서 삭제·수정 등 chunk 를 지우는 **모든 경로**가 이 비용을 냈고, 코퍼스가 커질수록 증분 재색인이 느려지는 형태였다.
**trade-off.** 없다. 컬럼 구성과 토크나이저가 그대로라 검색 결과가 바뀌지 않는다. 실제 KB 에서 네 질의의 상위 20건을 점수와 스니펫까지 해시로 비교했고 마이그레이션 전후가 동일했다.
**mitigation / upgrade.** 다음 실행 때 자동 적용된다. 60만 청크 기준 32초가 걸리고 **재색인은 필요 없다**`chunks` 와 임베딩은 손대지 않는다.
**도그푸딩.** 문서 28,427건 / 청크 600,808개 KB 에서 문서 200건 삭제:
| | 소요 |
|---|---|
| 이전 | 1,590초 |
| 이후 | **2.0초** |
약 800배다.
---
## 3. LanceDB 메타데이터가 제곱으로 불어나던 문제 (#230) — patch (동작 불변)
**변경 사실.** 벡터 스토어에 압축 정책이 생겼고, 삭제가 파일 단위에서 배치 단위로 바뀌었다.
압축이 없던 동안 매 쓰기가 fragment 와 "지금까지의 모든 fragment 를 나열한" manifest 를 남겼고, 메타데이터가 실데이터보다 빠르게 커졌다. 문서 1.1만 건에서 2.5배였던 비율이 1.7만 건에서 7.2배였다.
**trade-off.** 압축이 도는 순간에는 I/O 를 쓴다. 실측으로 15 GB 를 1.7 GB 로 되돌리는 데 153초였다.
**mitigation / upgrade.** 자동이다. 별도 조치가 없다.
**도그푸딩.** 2.8만 문서를 넣는 동안 색인 속도가 분당 32~36건으로 평평하게 유지됐다. 이전에는 30.7건에서 4.3건까지 단조롭게 떨어졌다. LanceDB 크기는 문서가 1.7배 늘었는데도 15 GB 에서 2.8 GB 로 줄었다. 문서 364건 삭제가 만드는 커밋은 364회에서 33회가 됐다.
`kebab doctor``vector_store` 정보성 체크가 생겨 fragment·버전 수를 보여준다(종료 코드 영향 없음).
---
## 4. 삭제 구간이 멈춘 것처럼 보이던 문제 (#228) — patch (관측성)
**변경 사실.** 파일이 지워진 문서를 정리하는 구간(sweep)이 진행 표시와 로그에 나온다.
이 구간은 신호를 하나도 내지 않았다. 진행바는 색인 총계를 표시한 채 멈춰 보이고 ndjson 로그는 0바이트로 남았다. 프로세스는 정상 동작 중인데 밖에서는 hang 과 구별할 수 없었고, 실제로 세 번 연속 Ctrl-C 로 죽인 기록이 이슈에 남아 있다.
**trade-off.** 없다. `--json` 에 이벤트 세 종(`sweep_started` / `sweep_progress` / `sweep_completed`)과 ndjson 로그에 세 종(`purge` / `purge_failed` / `sweep_summary`)이 **추가**된다. 기존 소비자는 모르는 종류를 무시하므로 영향이 없다.
**mitigation / upgrade.** 자동이다.
곁들여, sweep 이 취소 플래그를 보게 했다. 첫 Ctrl-C 가 "현재 파일까지만 하고 중단한다" 고 안내하는데 이 구간에서는 사실이 아니었다 — 유일한 수단인 두 번째 Ctrl-C 는 강제 종료라 버퍼에 쌓인 삭제가 고아로 남았다. 스캔 결과가 0건일 때 취소가 "완료" 로 보고되던 것도 함께 고쳤다.
---
## 5. 임베딩 캐시 (#231) — patch, 그리고 가설이 재현되지 않았다
이슈는 "캐시가 히트하는데도 우회하고 전량 재임베딩하는 편이 더 빠르다" 고 보고하면서 실측 표를 비워 뒀다. 그 표를 채웠는데 **재현되지 않는다.**
문서 792건 / 청크 16,379개를 강제 재처리한 결과, 캐시 히트 경로가 139초, 캐시 우회가 1,180초로 **히트가 8.5배 빠르다.** 새로 넣은 계측으로 뜯어보면 캐시 경로 전체가 run 의 1.5% 이하다.
다만 보고가 틀렸다고 단정하지 않는다. 보고 이후 #229#230 이 머지됐고, 이슈 본문 스스로 #229 와 같은 뮤텍스를 공유해 서로를 증폭한다고 적어 뒀다. 그러니 "#229 이후에는 재현되지 않는다" 로 읽는 것이 맞다.
**변경 사실.** 캐시 조회를 배치로 접고(문서당 왕복 21회 → 1회), 삽입을 한 트랜잭션으로 묶고, 구문 캐시를 쓴다. 다만 **벽시계 차이는 측정 오차 안**이다 — 이 코퍼스에서는 체감이 없다.
실질적인 산출물은 계측 쪽이다. `--json``asset_timings``cache_hit` / `cache_miss` / `cache_ms` 가 실린다. 이전에는 stderr 로만 흘러 run 이 끝나면 사라졌고, "내 코퍼스에서 캐시가 이득인가" 를 확인할 방법이 없었다. TTY 에서는 `⏱ … embed 75ms · cache 21/21 0ms · store 36ms` 로 보인다.
**trade-off / upgrade.** 없다. 자동이다.
---
## 그 밖
- `cargo clippy --workspace --all-targets -- -D warnings` 가 main 에서 실패하고 있었다(툴체인 상승으로 새 lint 두 개가 기존 코드에 걸림). 고쳤다. 방치하면 새 위반이 그 그늘에 숨는다는 것을 실제로 겪었다.
- `kebab doctor` 에 체크 세 개가 추가됐다: `pdf_render`(정보성), `vector_store`(정보성), `fts_shadow`(**어긋나면 exit 3** — 이 상태에서는 문서 삭제가 엉뚱한 인덱스 행을 지운다).