.cg-article{–accent:#4f46e5;–accent-soft:#eef2ff;–accent-line:#c7d2fe;–navy:#312e81;–warn:#d97706;–warn-soft:#fffbeb;line-height:1.75;color:#1f2937;font-size:17px;word-break:keep-all}
.cg-article h2{margin:2.2em 0 .7em;padding-left:.55em;border-left:5px solid var(–accent);font-size:1.42em;line-height:1.4;color:var(–navy)}
.cg-article h3{margin:1.6em 0 .5em;font-size:1.13em;color:var(–accent)}
.cg-article p{margin:.7em 0}
.cg-lead{background:var(–accent-soft);border:1px solid var(–accent-line);border-radius:12px;padding:18px 20px;margin:1.2em 0;font-size:1.02em}
.cg-lead strong{color:var(–accent)}
.cg-table{width:100%;border-collapse:collapse;margin:1.1em 0;font-size:.97em}
.cg-table th,.cg-table td{border:1px solid #e5e7eb;padding:11px 13px;text-align:left;vertical-align:top}
.cg-table th{background:var(–accent);color:#fff;font-weight:600}
.cg-table tr:nth-child(even) td{background:#f9fafb}
.cg-check{list-style:none;padding:0;margin:1.1em 0}
.cg-check li{position:relative;padding:9px 9px 9px 34px;margin:6px 0;background:var(–accent-soft);border-radius:8px}
.cg-check li::before{content:”✓”;position:absolute;left:12px;top:9px;color:var(–accent);font-weight:700}
.cg-warn{background:var(–warn-soft);border-left:5px solid var(–warn);border-radius:8px;padding:16px 18px;margin:1.3em 0}
.cg-warn strong{color:var(–warn)}
.cg-faq{margin:1.5em 0}
.cg-faq dt{font-weight:600;margin:1.1em 0 .3em;color:var(–accent)}
.cg-faq dd{margin:0 0 .6em}
.cg-note{font-size:.9em;color:#6b7280;border-top:1px dashed #d1d5db;margin-top:2.4em;padding-top:1em}
.cg-article pre{background:#1e1b34;color:#e0e7ff;border-radius:10px;padding:14px 16px;overflow-x:auto;font-size:.9em;line-height:1.6;margin:1.1em 0}
.cg-article pre code{background:none;color:inherit;padding:0;font-size:inherit}
.cg-article code{background:var(–accent-soft);color:var(–navy);padding:.1em .4em;border-radius:5px;font-size:.92em}
RAG(검색 증강 생성)에서 모델이 좋은 답을 내놓느냐는 결국 “검색 단계에서 올바른 문맥 조각을 가져왔는가”로 갈립니다. 그리고 그 조각의 모양을 결정하는 것이 청킹(chunking)입니다. 문서를 어떻게 잘라 두느냐에 따라 같은 질문에도 전혀 다른 검색 결과가 나옵니다. 너무 크게 자르면 한 청크에 여러 주제가 섞여 임베딩이 흐려지고, 너무 작게 자르면 답에 필요한 문맥이 끊깁니다. 이 글에서는 청킹이 검색 품질을 좌우하는 원리, 고정 크기·의미 단위·재귀 분할의 차이, 오버랩과 메타데이터의 역할, 청크 크기를 둘러싼 트레이드오프, 그리고 청킹을 수치로 평가하는 방법까지 실무 관점에서 정리합니다.
청킹이 검색 품질을 좌우하는 이유
RAG 파이프라인은 크게 색인 단계와 질의 단계로 나뉩니다. 색인 단계에서는 원본 문서를 청크로 자르고, 각 청크를 임베딩 모델에 넣어 벡터로 바꾼 뒤 벡터 데이터베이스에 저장합니다. 질의 단계에서는 사용자의 질문을 같은 임베딩 모델로 벡터화해 가장 가까운 청크들을 찾아오고, 그 청크를 프롬프트에 붙여 LLM에게 답을 생성하게 합니다. 즉 LLM이 보는 것은 원문 전체가 아니라 검색으로 추려진 몇 개의 청크뿐입니다.
여기서 핵심은 임베딩이 청크 단위로 만들어진다는 점입니다. 하나의 벡터는 그 청크 전체의 의미를 평균적으로 압축한 좌표입니다. 한 청크 안에 서로 다른 주제가 섞여 있으면, 그 벡터는 어느 주제와도 어중간하게 가까운 위치에 놓여 검색에서 밀려납니다. 반대로 한 문장만 담긴 청크는 의미는 또렷하지만 답을 구성하는 데 필요한 앞뒤 맥락이 빠져, 검색은 성공해도 LLM이 충분한 근거를 보지 못합니다. 결국 청킹은 “검색이 잘 되는 단위”와 “답을 만들기에 충분한 단위” 사이의 균형을 잡는 작업입니다.
많은 팀이 임베딩 모델이나 벡터 DB를 바꾸며 품질을 끌어올리려 하지만, 실제로는 청킹 전략을 손보는 것이 더 큰 효과를 내는 경우가 많습니다. 검색이 엉뚱한 조각을 가져오면 그 뒤의 모델이 아무리 좋아도 답이 틀리기 때문입니다.
고정 크기 청킹 — 가장 단순하고 빠른 기준선
고정 크기(fixed-size) 청킹은 문서를 일정한 글자 수 또는 토큰 수로 기계적으로 자르는 방식입니다. 구현이 단순하고 빠르며, 청크 크기를 정확히 통제할 수 있어 임베딩 모델의 입력 한도를 넘길 걱정이 없습니다. 처음 RAG를 구성할 때 기준선으로 삼기에 적합합니다.
from langchain_text_splitters import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=500, # 청크당 글자(또는 토큰) 수
chunk_overlap=80, # 인접 청크 간 겹치는 양
)
chunks = splitter.split_text(document)단점은 분명합니다. 문장이나 문단의 의미 경계를 무시하고 자르기 때문에, 한 문장이 두 청크로 쪼개지거나 표·코드 블록이 중간에서 끊기는 일이 생깁니다. 그래서 고정 크기 청킹을 쓸 때는 뒤에서 설명할 오버랩을 함께 두어, 경계에서 잘린 문맥이 양쪽 청크에 모두 남도록 보완하는 것이 일반적입니다. 토큰 기준으로 자를지 글자 기준으로 자를지는 사용하는 임베딩 모델의 토큰화 방식에 맞추는 편이 안전하며, 모델별 입력 토큰 한도는 OpenAI 임베딩 공식 문서 같은 공식 문서에서 확인하길 권합니다.
의미 단위 청킹 — 경계를 존중하는 분할
의미 단위(semantic) 청킹은 문단, 제목, 문장처럼 본래의 의미 경계를 따라 자르는 방식입니다. 마크다운의 헤더 구조나 HTML 태그, 문장 종결 부호를 활용해 “한 청크 = 하나의 완결된 의미 덩어리”가 되도록 만듭니다. 검색 결과로 가져온 청크가 그 자체로 읽히기 때문에 LLM이 맥락을 파악하기 쉽고, 임베딩도 또렷해집니다.
좀 더 발전된 형태로는 문장 단위 임베딩의 유사도를 이용해, 의미가 급격히 바뀌는 지점에서 청크를 끊는 임베딩 기반 의미 분할도 있습니다. 인접 문장 사이의 코사인 유사도가 임계값 아래로 떨어지면 새 청크를 시작하는 식입니다. 품질은 좋지만 색인 시 임베딩 계산이 추가로 들어가 비용과 시간이 늘어나므로, 문서 양과 갱신 빈도를 함께 따져 도입해야 합니다.
의미 단위 청킹의 약점은 청크 크기가 들쭉날쭉해진다는 점입니다. 짧은 문단과 긴 문단이 섞이면 어떤 청크는 너무 작고 어떤 청크는 모델 한도를 넘을 수 있습니다. 이를 보완하기 위해 의미 경계를 우선하되 크기 상한을 함께 거는 방식이 재귀적 분할입니다.
재귀적 분할 — 의미와 크기를 함께 잡는 절충안
재귀적(recursive) 분할은 구분자 우선순위 목록을 두고, 큰 단위부터 차례로 시도하면서 청크가 목표 크기 안에 들어올 때까지 더 작은 단위로 쪼개는 방식입니다. 보통 문단(빈 줄) → 줄바꿈 → 문장 → 단어 순서로 내려갑니다. 의미 경계를 최대한 지키면서도 크기 상한을 넘지 않아, 실무에서 가장 무난한 기본값으로 자주 쓰입니다.
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=600,
chunk_overlap=100,
separators=["nn", "n", ". ", " ", ""], # 큰 단위부터 시도
)
chunks = splitter.split_text(document)아래는 세 방식의 특성을 한눈에 비교한 표입니다.
| 방식 | 경계 존중 | 크기 일관성 | 색인 비용 | 적합한 경우 |
|---|---|---|---|---|
| 고정 크기 | 낮음 | 높음 | 낮음 | 형식이 일정한 대량 텍스트, 빠른 기준선 |
| 의미 단위 | 높음 | 낮음 | 중~높음 | 구조가 뚜렷한 문서(매뉴얼·문서화) |
| 재귀적 분할 | 중~높음 | 중~높음 | 낮음 | 대부분의 일반 문서, 무난한 기본값 |
청크 보강 — 오버랩과 메타데이터
분할 방식을 정했다면, 잘린 문맥을 살리고 청크에 맥락을 입히는 두 가지 보강 장치를 함께 적용합니다. 오버랩은 경계 손실을 줄이고, 메타데이터는 검색 정밀도와 근거 제시를 끌어올립니다.
오버랩 — 경계에서 잘린 문맥을 살리기
오버랩(overlap)은 인접한 청크끼리 일부 내용을 겹치게 두는 기법입니다. 청크 경계에서 문맥이 끊기는 문제를 완화하는데, 예를 들어 어떤 정의가 한 청크 끝과 다음 청크 시작에 걸쳐 있을 때 오버랩이 있으면 양쪽 청크가 모두 그 정의를 포함하게 됩니다. 검색이 둘 중 어느 청크를 가져오든 핵심 문맥을 놓치지 않습니다.
다만 오버랩을 키울수록 같은 내용이 여러 청크에 중복 저장되어 저장 공간과 임베딩 비용이 늘고, 검색 결과에 비슷한 청크가 중복으로 올라와 다양성이 떨어질 수 있습니다. 청크 크기의 대략 10~20% 수준에서 시작해 검색 품질을 보며 조정하는 것이 일반적인 출발점이며, 절대적인 정답값은 아닙니다. 문서가 짧고 자기완결적인 문단으로 이뤄져 있다면 오버랩을 작게, 설명이 길게 이어지는 산문이라면 조금 크게 두는 식으로 문서 성격에 맞춥니다.
메타데이터 부착 — 청크에 맥락을 입히기
청크를 저장할 때 본문 벡터만 넣지 말고, 출처 문서명·섹션 제목·작성일·문서 종류 같은 메타데이터를 함께 붙이면 검색의 정밀도가 크게 올라갑니다. 메타데이터는 두 가지로 쓰입니다. 첫째는 필터링입니다. “최근 1년 문서만”, “특정 제품 매뉴얼만”처럼 벡터 검색 전에 후보를 좁혀 엉뚱한 영역의 청크가 끼어드는 것을 막습니다. 둘째는 답변 근거 제시입니다. LLM이 답하면서 어느 문서의 어느 섹션을 참고했는지 출처를 함께 보여줄 수 있습니다.
{
"text": "청크 본문 ...",
"metadata": {
"source": "api-guide.md",
"section": "인증 토큰 발급",
"updated_at": "2026-05-01",
"doc_type": "manual"
}
}한 가지 실무 팁은, 청크 본문 맨 앞에 그 청크가 속한 상위 섹션 제목을 같이 넣어 두는 것입니다. 작은 청크가 독립적으로 검색돼도 “이게 무엇에 대한 설명인지”가 본문 안에 남아 임베딩이 또렷해지고, LLM도 맥락을 빠르게 잡습니다.
청크 크기와 검색 정확도의 트레이드오프
청크 크기는 RAG 품질에서 가장 자주 손대는 변수이면서, 한쪽으로 치우치면 반드시 대가가 따르는 전형적인 트레이드오프입니다. 작은 청크는 임베딩이 또렷해 검색 정밀도(가져온 것 중 맞는 비율)가 높지만, 답에 필요한 문맥이 여러 청크에 흩어져 재현율(필요한 것을 빠짐없이 가져오는 비율)이 떨어지고 검색해야 할 청크 수가 늘어납니다. 큰 청크는 한 번에 풍부한 맥락을 담지만, 여러 주제가 섞여 임베딩이 흐려지고 프롬프트에 들어가는 토큰이 늘어 비용과 지연이 커집니다.
| 구분 | 작은 청크 | 큰 청크 |
|---|---|---|
| 임베딩 선명도 | 높음(주제 단일) | 낮음(주제 혼재) |
| 검색 정밀도 | 높은 편 | 낮아질 수 있음 |
| 문맥 충분성 | 부족하기 쉬움 | 풍부함 |
| 프롬프트 토큰·비용 | 적음 | 많음 |
| 적합 | FAQ·정의·짧은 사실 | 설명·절차·서사형 문서 |
실무에서는 “검색은 작은 청크로, 생성은 큰 맥락으로” 분리하는 패턴도 자주 씁니다. 작은 청크로 검색해 정밀도를 확보한 뒤, 그 청크가 속한 상위 문단이나 부모 문서를 함께 가져와 LLM에 넘기는 방식입니다. 검색의 선명함과 생성의 풍부함을 모두 챙기려는 접근입니다.
청킹 전략을 평가하는 방법
청킹은 감으로 결정할 영역이 아니라, 측정해서 비교할 대상입니다. 핵심은 실제 사용자가 던질 법한 질문과, 그 질문에 답이 되는 정답 청크(또는 정답 문서)를 짝지은 평가 셋을 만드는 것입니다. 그런 다음 청킹 설정을 바꿔 가며 같은 질문으로 검색했을 때 정답 청크가 상위 결과에 들어오는지 측정합니다.
대표적인 지표는 다음과 같습니다.
- Recall@k — 상위 k개 검색 결과 안에 정답 청크가 포함된 질문의 비율. 검색이 필요한 것을 놓치지 않는지를 봅니다.
- Precision@k — 상위 k개 중 실제로 관련 있는 청크의 비율. 군더더기 없이 정확히 가져오는지를 봅니다.
- MRR(평균 역순위) — 정답 청크가 결과의 몇 번째에 나오는지를 반영해, 정답을 얼마나 앞쪽에 올리는지를 봅니다.
- 최종 답변 품질 — 검색이 아니라 LLM의 최종 답을 사람이나 평가 모델이 채점해, 청킹 변화가 답까지 개선했는지 확인합니다.
평가 셋은 처음부터 클 필요가 없습니다. 실제 운영 로그에서 자주 들어온 질문 30~50개로 시작해도 청킹 설정 간 우열을 가리는 데 충분한 경우가 많습니다. 청크 크기, 오버랩, 분할 방식을 한 번에 하나씩만 바꿔 가며 같은 평가 셋으로 비교하면, 어떤 변수가 품질을 끌어올렸는지 명확히 드러납니다.
임베딩 모델을 바꾸면 전체 재색인이 필요합니다.
청킹 설정이나 임베딩 모델을 변경하면, 기존에 저장한 벡터는 새 기준과 호환되지 않습니다. 질의 임베딩과 색인 임베딩은 반드시 같은 모델·같은 전처리로 만들어야 하므로, 변경 시에는 전체 문서를 다시 청킹하고 다시 임베딩해야 합니다. 문서 양이 많다면 재색인 비용과 다운타임을 미리 계획에 넣으세요.
실무 적용 체크리스트
- 재귀적 분할을 기본값으로 두고, 문서 성격에 따라 의미 단위·고정 크기를 선택적으로 적용했는가
- 청크 크기를 사용하는 임베딩 모델의 입력 토큰 한도 안으로 맞췄는가(한도는 공식 문서 확인)
- 오버랩을 청크 크기의 10~20% 정도에서 시작해 품질을 보며 조정했는가
- 출처·섹션·작성일 등 메타데이터를 붙이고, 청크 앞에 상위 제목을 넣었는가
- 표·코드 블록이 중간에서 잘리지 않도록 구분자나 전처리를 점검했는가
- 질문-정답 청크 평가 셋을 만들어 Recall@k 등으로 설정 간 비교를 했는가
- 변수를 한 번에 하나씩만 바꿔 가며 영향을 측정했는가
자주 묻는 질문
- 청크 크기는 몇으로 시작하는 게 좋나요?
- 문서 성격에 따라 다르지만, 재귀적 분할로 수백 토큰 규모에서 시작해 평가 셋으로 조정하는 방식이 무난합니다. 절대값보다 “내 데이터로 측정해 비교한다”는 절차가 더 중요합니다. 사용하는 임베딩 모델의 입력 한도는 공식 문서에서 확인하세요.
- 오버랩이 클수록 검색이 좋아지나요?
- 일정 수준까지는 경계 문맥 손실을 줄여 도움이 되지만, 지나치면 중복 청크가 늘어 저장·임베딩 비용이 오르고 검색 결과 다양성이 떨어집니다. 청크 크기의 10~20% 부근에서 시작해 효과를 보며 늘리거나 줄이세요.
- 의미 단위 청킹이 항상 더 좋은가요?
- 경계를 존중해 품질이 좋은 편이지만 청크 크기가 불균일해지고 색인 비용이 늘 수 있습니다. 크기 상한이 중요하면 재귀적 분할이, 구조가 뚜렷한 문서라면 의미 단위가 유리합니다. 데이터로 비교해 결정하는 것이 정확합니다.
- 표나 코드가 중간에서 잘리는 문제는 어떻게 막나요?
- 마크다운·HTML 구조를 인식하는 스플리터를 쓰거나, 표·코드 블록을 하나의 단위로 보존하도록 구분자를 조정합니다. 전처리 단계에서 코드 펜스나 표를 별도 청크로 떼어 두는 방법도 흔히 씁니다.
- 청킹을 바꾸면 기존 벡터는 그대로 써도 되나요?
- 안 됩니다. 청킹이나 임베딩 모델이 바뀌면 기존 벡터와 호환되지 않으므로 전체 재색인이 필요합니다. 질의와 색인은 반드시 같은 임베딩 기준으로 맞춰야 검색이 정상 동작합니다.
이 글은 RAG 파이프라인에서 청킹이 검색 품질에 미치는 영향을 일반적인 원리 중심으로 정리한 기술 참고 자료입니다. 임베딩 모델의 입력 토큰 한도, 처리 단가, 권장 설정은 모델과 제공사·시점에 따라 달라지므로, 실제 구현 전에 사용하는 임베딩 모델과 프레임워크(예: LangChain, LlamaIndex)의 공식 문서를 확인하시기 바랍니다. 본문의 코드는 개념 설명용 예시이며 버전에 따라 API가 달라질 수 있습니다.