검색

Chunking

긴 문서를 검색 단위로 잘라 RAG 검색 품질을 좌우하는 전처리 단계

입문

한 줄 정의

청킹은 긴 문서를 검색과 임베딩이 가능한 크기의 조각으로 자르는 작업이다.

왜 필요한가

문서 전체를 통째로 임베딩하면 두 가지 문제가 생긴다.

  • 검색 정확도가 떨어진다. 200페이지 문서 하나가 벡터 하나가 되면 그 안의 특정 문단을 찾을 수 없다
  • 컨텍스트 윈도우를 넘긴다. 검색 결과를 프롬프트에 넣어야 하는데 문서 전체는 들어가지 않는다

반대로 너무 잘게 자르면 문맥이 끊겨서, 조각만 읽어서는 무슨 말인지 알 수 없게 된다.

청킹은 이 둘 사이의 균형을 잡는 작업이다. RAG에서 검색 품질을 가장 크게 좌우한다.

핵심 개념

용어설명
chunk size조각 하나의 크기. 보통 토큰 또는 문자 수
overlap인접한 조각이 겹치는 구간. 경계에서 문맥이 끊기는 것을 막는다
separator자르는 기준. 문단(\n\n) → 문장(.) → 단어 순으로 시도

동작 구조

flowchart LR
    D[원본 문서] --> S[구분자로 분할]
    S --> C1[청크 1]
    S --> C2[청크 2]
    S --> C3[청크 3]
    C1 --> E[임베딩]
    C2 --> E
    C3 --> E
    E --> V[(벡터 DB)]

예제

chunk_size=500, overlap=50으로 자른다고 하자.

청크 1: 0    ~ 500
청크 2: 450  ~ 950     ← 앞 50자가 청크 1과 겹친다
청크 3: 900  ~ 1400

겹침이 없으면 500번째 글자에서 문장이 잘려 두 청크 모두 의미가 불완전해진다.

코드

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,
    # 문단 → 줄 → 문장 → 단어 순으로 자를 곳을 찾는다
    separators=["\n\n", "\n", ". ", " ", ""],
)

chunks = splitter.split_text(document)
print(f"{len(chunks)}개 청크")

RecursiveCharacterTextSplitter는 구분자를 순서대로 시도해 가능한 한 의미 단위를 지키며 자른다. 단순히 글자 수로 자르는 것보다 문맥 보존이 낫다.

장점

  • 검색 단위가 작아져 정확도가 오른다
  • 프롬프트에 필요한 부분만 넣어 비용이 준다
  • 겹침으로 경계 문맥 손실을 줄인다

단점

  • 크기와 겹침을 정하는 정답이 없어 실험이 필요하다
  • 겹침이 크면 저장 용량과 임베딩 비용이 늘어난다
  • 표·코드 블록이 중간에서 잘리면 의미가 깨진다

자주 발생하는 문제

증상원인해결
검색 결과가 문맥이 끊겨 있다청크가 너무 작거나 겹침이 없다overlap을 chunk_size의 10~20%로
관련 없는 내용이 섞여 나온다청크가 너무 크다chunk_size를 줄인다
표가 깨진다구분자가 표 구조를 무시한다표는 별도 파서로 분리 후 청킹
코드 예제가 잘린다코드 블록 경계를 모른다마크다운 인식 splitter 사용

실제 프로젝트 적용

아직 없음.

관련 문서

참고 자료