v1 의 "블록 미분할" 한계를 일반화. 거대 list/code/table/paragraph 가 한 청크로
임베더 컨텍스트를 초과해 임베딩이 통째로 실패하던 문제를 해소한다. v2 는 v1 과
모든 출력이 동일하되, 청크의 실제 임베드 text 크기(`text.len()/3`)가
`max_chunk_tokens`(신규 config, byte/3, default 4000)를 넘는 청크만 줄(`\n`)
경계로, 단일 거대 줄은 UTF-8 char 경계로 잘라 각 조각이 예산 이하가 되게 한다.
- 판정 기준은 저장 `token_estimate` 가 아니라 실제 `text` 길이: ImageRef/AudioRef
청크는 image-only 규약으로 token_estimate=0 이지만 OCR/caption text 는 클 수
있다(빽빽한 스크린샷). 도그푸딩 일치 재테스트에서 이 image-OCR 구멍 발견·수정.
- 분할 조각 chunk_id 는 동일 block_ids 를 공유하므로 id-input 해시에 `#seg{i}`
접미사로 충돌 회피(저장 policy_hash 는 bare — pdf-page-v1 의 `#L` 레시피 동형).
- `max_chunk_tokens` 는 v2 의 policy_hash 에만 fold(공유 ChunkPolicy 미변경 →
코드/PDF 청커 cascade 무영향). 값 변경 시 markdown 만 재청크.
- 미분할 청크는 v1 과 byte-identical. `chunker_version` v1→v2 → 다음 plain
`kebab ingest` 에서 markdown 1회 자동 재청크(--force 불필요, 코드/PDF 무영향).
동기: strict 임베더(AMD Lemonade `/api/embed`)는 oversize 입력을 truncate 아닌
거부(`500 too large`) — ollama 가 조용히 truncate 하던 걸 청커가 애초에 안 만들게.
검증(실험 KB, arctic-embed-l-v2 @ Lemonade): v2 전 620 중 2 doc(거대 jira list
블록) 임베드 실패 → v2 후 620/620 errors=0, 7114 청크 전부 ≤ 4000. 사용자 실
config 일치 재테스트(이미지 OCR + PDF OCR paddle-onnx ON)에서 image-OCR 구멍
발견·수정 후 dense 이미지 OCR 텍스트가 budget 초과 시 분할(token_estimate=0 →
1청크였던 것이 실제 text 기준 다중 청크로) 실증. frozen 설계 doc / frozen p1-5
spec 미변경(설계 §9 가 md-heading-v2 라벨 bump 를 변경 메커니즘으로 명시).
known limitation: PDF 는 별도 청커 pdf-page-v1.1 이라 이 split 미적용(후속 후보).
Cargo.toml 0.29.0 → 0.30.0 (신규 config 키 + 청커 동작 변경 = pre-1.0 minor +
도그푸딩 트리거). docs cascade: HOTFIXES / release-notes-v0.30.0-draft /
plan(2026-06-24) / normalize-chunk README / README / SMOKE / HANDOFF.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012Mc6W1fgsrbFKTsqA6P8La
5.9 KiB
title, created, status, release_trigger
| title | created | status | release_trigger | ||
|---|---|---|---|---|---|
| kebab v0.30.0 release notes (draft) | 2026-06-24 | draft |
|
kebab v0.30.0 — md-heading-v2: 거대 청크가 임베딩을 깨지 않도록
v0.29.0(provenance 출처 필터) 후속 minor release. markdown 청커가 하나의 거대
블록이 임베더 컨텍스트를 초과해 임베딩을 통째로 실패시키던 문제를 해소한다.
긴 로그·스택트레이스·표가 통으로 들어간 문서(예: jira 이슈)를 색인할 때 그
문서가 검색에서 통째로 사라지던 회귀를 막는다. 일반 사용자는 업그레이드 후
다음 kebab ingest 한 번이면 끝 — markdown 자산만 1회 자동 재청크된다.
변경 사실
1) md-heading-v2 청커가 기본값이 됐다. v1은 코드/테이블 블록을 (크기와
무관하게) 절대 분할하지 않았고, list·paragraph도 블록 하나가 곧 청크 하나였다.
그래서 거대한 단일 블록 — 예컨대 로그 덤프가 1000줄짜리 불릿 리스트로 변환된 것 —
이 임베더의 입력 한도를 넘으면 그 문서 전체의 벡터 색인이 실패했다. v2는 v1과
모든 출력이 동일하되, 마지막에 예산을 넘는 청크만 줄(\n) 경계로,
한 줄이 홀로 예산을 넘으면 UTF-8 문자 경계로 잘라 각 조각이 예산 이하가 되게 한다.
2) 신규 config [ingest.chunking] max_chunk_tokens (byte/3 토큰, default
4000). 이 값을 넘는 청크가 분할 대상이 된다. 4000은 8192-context 임베더(예:
snowflake-arctic-embed2)에 약 2배 안전마진을 두면서 일반적인 코드·문단은 그대로
한 청크로 유지하는 값이다. kebab config migrate로 기존 config에 additive 주입.
[ingest.chunking]
chunker_version = "md-heading-v2"
max_chunk_tokens = 4000
3) 분할 조각의 chunk_id·citation. 한 블록에서 나온 분할 조각들은 같은
block_ids를 공유하므로 chunk_id에 #seg{i} 접미사를 붙여 충돌을 막는다. 분할
조각의 citation은 블록 단위(원 블록 범위) — sub-line 정밀도는 아니다.
미분할 청크(대다수)는 v0.29.0과 출력·citation이 완전히 동일하다.
Trade-off
- 거대 블록이 여러 검색 hit로 나뉜다. 이전엔 그 문서가 (임베딩 실패로) 벡터 검색에서 아예 안 나왔거나, ollama처럼 조용히 잘린 채 한 hit였다. 이제는 여러 조각으로 정상 색인되어 각각 hit로 잡힐 수 있다 — 같은 문서가 상위에 여러 번 보일 수 있다.
- 분할 조각 citation은 블록 단위다. fenced 코드의 줄 span이 fence를 포함하는 비대칭 때문에 조각별 정확한 줄 범위를 (span, code)만으로 복원할 수 없어, "절대 틀리지 않되 블록 단위"를 택했다. 정밀 sub-line citation이 필요하면 향후 파서가 content 줄 범위를 직접 제공하는 개선이 필요하다.
- byte/3 휴리스틱. 토큰 수는 실제 토크나이저가 아니라 바이트/3 근사다. CJK는 과대추정(더 안전), 영문 코드는 실토큰과 비슷. strict 임베더에 대해 보수적이다.
Mitigation
- 결과·CLI·wire 포맷 불변.
--json스키마, exit code, citation 모양 모두 동일. 내부적으로 거대 블록이 분할될 뿐이다. - 공유
ChunkPolicy미변경.max_chunk_tokens는 v2 청커의 policy_hash에만 fold되어 코드·PDF 청커의 cascade를 건드리지 않는다 — 코드/PDF 자산은 재청크되지 않는다. - strict 임베더 호환. v1 시절 ollama가 조용히 truncate하던 것을 청커가 애초에
예산 이하로 만들므로, oversize 입력을 truncate가 아닌 거부(
500 too large)로 처리하는 백엔드(예: AMD Lemonade)에서도 색인이 깨지지 않는다. - 이미지 OCR 텍스트도 보호. 분할 판정은 청크의 실제 임베드 text 크기로 한다 — 이미지 청크는 내부적으로 token_estimate가 0이지만 그 OCR/캡션 text는 클 수 있어 (빽빽한 스크린샷), 실제 text 길이로 판정해야 oversize 이미지 OCR도 분할된다.
Known limitation
PDF는 별도 청커(pdf-page-v1.1)를 쓰므로 이 oversize-split이 적용되지 않는다 —
초고밀도 scanned page 한 장의 OCR이 한 청크로 예산을 넘으면 그대로 임베드된다
(일반 PDF는 무관). 후속 작업으로 PDF 청커에도 동일 분할을 넣을 수 있다.
업그레이드 절차
- 새 바이너리로 교체.
- (선택)
kebab config migrate— config 파일에max_chunk_tokens = 4000을 additive 주입(주석·값 보존). 안 해도 기본값으로 동작한다. kebab ingest—chunker_version이md-heading-v1→md-heading-v2로 바뀌었으므로 markdown 자산이 1회 자동 재청크된다(--force-reingest불필요). 코드/PDF 자산은 영향 없음. embedding은 파생물 캐시(V012) 히트로 대부분 재계산을 피하지만, 분할된 거대 블록의 새 조각은 새로 임베드된다.- 대용량 KB라면 첫 ingest가 markdown 재청크로 평소보다 길어질 수 있다(1회성).
도그푸딩 evidence
실험 KB(MongoDB 문서 220 + jira 400, arctic-embed-l-v2 @ Lemonade GPU). v2 전:
620 문서 중 2개(SERVER-22906=76189토큰 list, SERVER-23097=20643토큰 list)가
임베드 실패. v2 후: 620/620, errors=0, 전 코퍼스 7114 청크가 모두 ≤ 4000
(초과 0). SERVER-22906은 14 청크로, SERVER-23097은 5 청크로 분할. "WiredTiger
excessive memory cache size" 질의에서 그동안 색인조차 안 되던 SERVER-22906이
**1위 결과(0.977)**로 — "임베드 불가"에서 "최상위 검색 결과"로 전환됐다. 출처
필터(--trust-min primary 등)는 정상 유지.