"RAG 핵심 학습 (5/26) — 청킹 전략의 5가지 길"
문서가 마크다운으로 들어오고, 분리 경계까지 잡혔다. 이제 "한 임베딩이 무엇을 한 단위로 볼 것인가"를 정한다.
청킹은 RAG에서 가장 짧은 결정처럼 보이지만 가장 광범위한 결과를 만든다. 같은 코퍼스, 같은 임베딩, 같은 LLM이라도 청킹 전략 하나로 retrieval 정확도가 10~30%p 흔들린다(Anthropic Contextual Retrieval 보고, RAGAS 벤치마크 다수). 본 편은 다섯 길 — Fixed / Paragraph / Heading / Semantic / Sliding Window — 와, 3편이 남긴 Whole-document 결정 표를 한 자리에서 풀어 낸다.
0. Prerequisites
- 2편(마크다운 변환), 3편(Ingestion 설계).
- 임베딩 모델의 max sequence length 개념(보통 512~8192 토큰).
- 토큰 ≠ 문자 — 한국어 1글자가 평균 1.3~2 토큰.
1. 학습 목표
- 다섯 청킹 전략의 한 줄 차이를 안다.
- chunk_size와 overlap의 효과와 trade-off를 식 한 줄로 설명한다.
- Whole-document 채택 기준을 표로 안다.
2. 핵심 요약
청크는 임베딩의 단위이자 retrieval이 가져오는 단위다. 너무 작으면 의미가 조각나 신호가 약해지고, 너무 크면 Lost in the Middle과 max length 초과를 만난다. Fixed(고정 토큰)는 단순·재현 좋지만 의미 경계를 깬다. Paragraph(문단)는 일반 텍스트에 잘 맞지만 길이 편차가 크다. Heading(섹션 제목)은 문서가 구조적일 때 강하다. Semantic(의미 경계 검출)은 텍스트의 의미가 바뀌는 지점에서 자른다. Sliding Window는 인접 청크를 겹쳐 경계 손실을 줄인다. RecursiveTextSplitter는 위 다섯을 우선순위로 시도하는 범용 표준이다. 그리고 작고 의미 단위가 분명한 문서는 Whole-document가 답일 수 있다 — 3편 §35-1의 핵심.
3. 직관 — 같은 문단을 다섯 방식으로 자른다
"보안 정책 5.2 권한 — 모든 직원은 최소 권한 원칙에 따라 …" 한 페이지짜리 정책 문서를 가정하자.
- Fixed(512 토큰): 한 문장 중간에서도 자른다. 청크 경계가 "최소 권한 원칙에 따라" 같은 어색한 지점에 떨어질 수 있다.
- Paragraph: 문단 단위. 길이가 80~500 토큰까지 편차가 크다.
- Heading:
## 5.2 권한같은 헤딩에서만 자른다. 한 청크가 한 절과 같아진다. - Semantic: 문장 임베딩의 코사인 거리가 크게 튀는 지점에서 자른다. 의미가 바뀌는 자리.
- Sliding Window: Fixed의 변형. 인접 청크가 50~100 토큰 겹친다.
같은 문장이라도 청크 안에서 어디에 위치하는지가 달라지며, 그에 따라 임베딩이 보는 신호가 달라진다.
4. 정의 — 다섯 전략 + Whole-document
| 전략 | 분할 기준 | 강점 | 약점 |
|---|---|---|---|
| Fixed | 토큰 N개마다 | 단순·재현 | 의미 경계 깸 |
| Paragraph | \n\n 또는 문단 마커 |
의미 보존 | 길이 편차 |
| Heading | # / ## / ### 등 마크다운 헤더 |
구조 문서에 강함 | 헤더 없는 문서 약함 |
| Semantic | 인접 문장 임베딩 거리 임계 | 의미 변화에 민감 | 임베딩 호출 비용 |
| Sliding Window | Fixed + overlap | 경계 손실 감소 | 인덱스 크기 ↑ |
| Whole-document | 자르지 않음 | 짧은·단단한 문서에 최선 | 긴 문서엔 부적합 |
RecursiveTextSplitter(LangChain 표준)는 우선순위 리스트(예: ["\n\n", "\n", ". ", " "])를 차례로 시도해 제일 큰 의미 경계부터 자른다. 위 다섯의 직렬 조합에 가깝다.
5. 식 — chunk_size · overlap의 의미
청킹의 trade-off는 두 변수로 정리된다.
- \(L\) = chunk_size (토큰 수)
- \(o\) = overlap (토큰 수)
- \(N_{\text{doc}}\) = 문서의 토큰 수
- \(N_{\text{chunks}}\) = 청크 수
$$N_{\text{chunks}} \approx \frac{N_{\text{doc}}}{L - o}$$
- \(L\)이 작으면 \(N_{\text{chunks}}\)가 크다 — 인덱스가 커지고 검색 비용↑, 그러나 세밀한 매칭이 가능.
- \(L\)이 크면 \(N_{\text{chunks}}\)가 작다 — 인덱스가 작고 빠르지만, 한 청크 안에 너무 많은 주제가 섞여 임베딩이 흐려진다.
- \(o\)가 크면 경계 손실은 줄지만 중복이 늘어 인덱스가 부풀고, 답 중복도 늘어난다.
실무 표준: - \(L\) = 256~1024 토큰 (영어 ~ 한국어 모두). - \(o\) = \(L\)의 10~20% (예: \(L=512\)이면 \(o=50~100\)). - 임베딩 모델의 max sequence length를 초과하지 않도록.
이 표준은 시작점이다. 14편(평가셋)·15편(검색 품질)에서 측정해서 조정한다.
6. 원리 워크스루 — RecursiveTextSplitter 한 흐름
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=800,
chunk_overlap=120,
separators=["\n\n", "\n", ". ", " "],
)
chunks = splitter.split_text(markdown_text)
내부에서 일어나는 일.
\n\n으로 문단을 우선 시도. 청크가chunk_size이하면 그대로 채택.- 아직 크면
\n(줄)로 다시 분할. - 아직 크면
.(문장)로 분할. - 아직 크면 공백 단위.
- 인접 청크에 overlap만큼 앞 청크의 끝 토큰을 붙인다.
이 우선순위가 Paragraph → Heading → Sentence → Token과 비슷한 효과를 만든다. Semantic과 Sliding은 별도 splitter(SemanticChunker, TokenTextSplitter 등)로 호출.
7. 변형과 사례
7.1 Fixed — 가장 단순
- 무엇이 바뀌나: 토큰 N개마다 자른다.
- 왜 쓰나: 재현성과 균일성. 검색 효율 분석이 단순.
- 무엇이 가능해졌나: 빠른 인덱스 빌드, 비용 추정 용이.
- 어디에 적합한가: 텍스트가 고르게 흐르는 영문 코퍼스. 빠른 POC.
- 한계: 의미 경계 깨짐. 한국어처럼 조사·어미가 의미를 결정하는 언어에서 더 큰 손실.
7.2 Paragraph — 문단 단위
- 무엇이 바뀌나:
\n\n같은 문단 구분에서 자른다. - 왜 쓰나: 사람이 작성한 텍스트는 문단이 의미 단위인 경우가 많다.
- 무엇이 가능해졌나: 의미 보존이 좋아져 임베딩 신호가 강해짐.
- 어디에 적합한가: 블로그·에세이·일반 보고서.
- 한계: 문단 길이 편차. 짧은 문단이 과도하게 많은 청크를 만들 수 있음.
7.3 Heading — 섹션 단위
- 무엇이 바뀌나: 마크다운 헤더(
#,##,###)에서 자른다. - 왜 쓰나: 기술 문서·매뉴얼은 섹션이 의미 단위.
- 무엇이 가능해졌나: 한 청크가 한 절과 같아져 답이 섹션 단위로 잘 나옴.
- 어디에 적합한가: 기술 문서, 정책, API docs.
- 한계: 헤더가 없는 문서에선 적용 불가. 한 섹션이 너무 길면 max length 초과.
7.4 Semantic — 의미 변화 지점
- 무엇이 바뀌나: 인접 문장의 임베딩 코사인 거리가 임계 이상으로 튀는 지점에서 자른다(SemanticChunker, Greg Kamradt의 LangChain 구현).
- 왜 쓰나: 사람이 보기엔 주제가 바뀌는 자리가 형식적 경계(빈 줄)와 일치하지 않을 때.
- 무엇이 가능해졌나: 형식이 불규칙한 텍스트에서도 의미 경계 유지.
- 어디에 적합한가: 회의록·인터뷰 transcript·뉴스 기사.
- 한계: 각 문장에 임베딩 1회 호출 — 코스트가 들고, 임베딩 변경 시 재청킹 필요.
7.5 Sliding Window — Overlap
- 무엇이 바뀌나: Fixed의 변형. 인접 청크가 o만큼 겹친다.
- 왜 쓰나: 청크 경계에 정답 정보가 걸쳤을 때, 단일 청크가 반토막 신호를 갖는 사고 방지.
- 무엇이 가능해졌나: 경계 가까이의 답이 두 청크 모두에 잡힘.
- 어디에 적합한가: 거의 모든 코퍼스에 기본값으로 권장.
- 한계: 인덱스 크기·중복 답 ↑. overlap이 너무 크면 동일 청크의 다른 잘림이 top-K를 점유.
7.6 Whole-document — 자르지 않는다
- 무엇이 바뀌나: 문서 전체를 한 임베딩으로.
- 왜 쓰나: 짧고 의미 단위가 분명한 문서(FAQ, README, 한 페이지 정책, 회의록 한 건). 청킹이 의미를 조각내는 경우.
- 무엇이 가능해졌나: 검색이 문서 단위 — 답에 사용할 컨텍스트가 문서 한 통째.
- 어디에 적합한가: §35-1이 정의한 기준 — 짧고, 한 가지 주제, 단단한 의미 단위.
- 한계: 긴 문서엔 max length로 적용 불가. 긴 문서는 섞어 쓸 수 있다(문서 단위 + 청크 단위 retrieval 공존).
Whole-document 채택 결정 표 (3편 8.5절 cross-ref):
| 문서 크기 | 의미 단위 | 권장 |
|---|---|---|
| < 1K 토큰 | 단단 (FAQ/README/한 페이지 정책) | Whole-document |
| 1~4K 토큰 | 단단 (회의록 한 건/논문 abstract) | Whole-document or Heading |
| 4~16K 토큰 | 섹션 구조 명확 | Heading + Sliding |
| > 16K 토큰 | 다중 주제 | RecursiveTextSplitter + Sliding |
8. 한계와 실패 양상
8.1 한 의미 단위가 두 청크로 반토막
- 왜 본질적인가: 청크 경계가 문장 가운데에 떨어지면 주어와 술어가 분리. 임베딩이 두 청크 모두에서 흐려진다.
- 진단: 정답 인용이 두 청크에 걸쳐 있는데 top-K가 하나만 가져오는 케이스.
- 완화: Sliding Window overlap 키움, Heading 우선.
- 다음 편: 6편 Contextual Chunking.
8.2 청크가 너무 작아 주제 분산
- 왜 본질적인가: 짧은 청크에는 한 주제만 들어가지 않고 문장 한 두 개 단위로 잘려 맥락 부족.
- 진단: top-K가 연관 단어는 매칭하지만 답을 구성하기엔 부족.
- 완화: chunk_size를 키움. Heading 또는 Paragraph로 전환.
- 다음 편: 6편 Parent-Child Retrieval.
8.3 청크가 너무 커 임베딩 흐림
- 왜 본질적인가: 한 청크에 여러 주제가 섞이면, 임베딩이 주제 평균으로 흐려져 어떤 주제와도 잘 매칭되지 않음.
- 진단: 같은 query에 top-K가 덤덤하게 비슷한 점수로 줄지어 나옴(불선명).
- 완화: chunk_size를 줄임. Semantic chunking.
- 다음 편: 7편 임베딩 모델.
8.4 한국어 조사 분리 손실
- 왜 본질적인가: 한국어는 조사("에서", "으로")가 의미를 결정. 토큰 단위 자르기가 조사를 본문에서 떼어내면 의미가 깨진다.
- 진단: 청크 끝에 어미가 잘려 있음.
- 완화: 형태소 분석기로 조사 경계를 청크 경계로 강제(Mecab/Kiwi), 또는 sentence-tokenizer 우선.
- 다음 편: 11편 BM25(한국어 형태소), 외전 D 한국어 RAG.
8.5 청크 후 메타데이터 누락
- 왜 본질적인가: 청크가 어느 문서·어느 페이지·어느 섹션에서 왔는지 metadata에 복제되지 않으면 필터링 불가.
- 진단: 청크 metadata에
document_id,section,page,version이 빠짐. - 완화: 청크 생성 시 모든 metadata를 청크 단위로 복제.
- 다음 편: 7편 메타데이터.
8.5 Common Pitfalls
- "청크 작을수록 좋다" — 8.2절.
- "청크 클수록 더 많은 정보" — 8.3절.
- "overlap 0이 깔끔하다" — 8.1절. Sliding Window가 거의 기본값.
- "한 번 청킹 = 영원히" — 임베딩 모델 변경·코퍼스 재구성 시 재청킹 필요.
- "한국어도 영어 splitter로" — 조사 손실. 8.4절.
9. 정리된 결론
Q1. RecursiveTextSplitter의 우선순위는 무엇을 흉내내나?
문단(\n\n) → 줄(\n) → 문장(.) → 단어/공백. 큰 의미 경계부터 시도해 최대 길이를 넘지 않게 자르는 흐름.
Chapter: §6.
Q2. chunk_size 512에 overlap 50을 쓰면 청크 수의 근사식은?
\(N_{\text{chunks}} \approx N_{\text{doc}} / (512 - 50) = N_{\text{doc}} / 462\). Chapter: §5.
Q3. Whole-document 채택 기준을 한 줄로 말하라.
짧고, 의미 단위가 단단하며, 한 가지 주제를 답하는 문서. Chapter: 7.6절, §35-1.
Q4. Semantic Chunking이 언제 비싸지는가?
각 문장에 임베딩 1회 호출이 필요하므로, 큰 코퍼스에서 임베딩 호출 비용이 누적된다. Chapter: 7.4절.
Q5. 청크 한 단위가 두 청크로 반토막 났을 때 가장 일반적인 완화는?
Sliding Window overlap을 키운다(예: 10% → 20%) 또는 Heading 기반 청킹으로 전환. Chapter: 8.1절.
10. 추가 학습
1차 자료
- LangChain. Recursive Character Text Splitter 문서·코드.
- Anthropic. Introducing Contextual Retrieval (2024-09 blog) — chunk 직전 LLM contextual prefix.
- Kamradt, G. 5 Levels of Text Splitting (2024 LangChain talk) — Semantic chunker 발상.
- Microsoft. Markdown Header Text Splitter (LangChain integrations) — Heading 청킹.
공식 docs
- LangChain Text Splitters:
https://python.langchain.com/docs/concepts/text_splitters/ - LlamaIndex NodeParsers:
https://docs.llamaindex.ai/en/stable/module_guides/loading/node_parsers/ - Pinecone Chunking Strategies:
https://www.pinecone.io/learn/chunking-strategies/
보조 자료
- 사용자 노트 3장·4장 — 청크와 overlap.
- 사용자 노트 §35-1 — Whole-document Retrieval.
Cheat Sheet
| 전략 | 시작 값 | 적합 |
|---|---|---|
| Fixed | 512 / overlap 0 | POC, 균일 영어 |
| Paragraph | 자연 문단 | 블로그/에세이 |
| Heading | 마크다운 헤더 | 기술 문서/매뉴얼 |
| Semantic | 임계 0.5~0.7 (cosine) | 회의록/transcript |
| Sliding | 512 / overlap 50~100 | 거의 모든 경우 권장 |
| Whole-document | — | < 1K 토큰의 단단한 문서 |
Bridge — 다음 편
다음 — RAG 핵심 학습 (6/26) 문맥 보강 청킹: Parent-Child & Contextual Retrieval.
청크 자체로 부족할 때, 부모 문서나 LLM이 만든 컨텍스트 접두사로 보강한다. Anthropic Contextual Retrieval(2024-09) — recall 35% 개선 보고의 핵심을 푼다. Parent-Child Retrieval, Chunk Header Injection, Document Summary Prefix까지.
시리즈 전체 안내: 시리즈 목차
댓글
댓글 쓰기