RAG 시스템 청크 크기 최적화 실험 기록

문제 상황

사내 기술 문서 검색 시스템에 RAG를 도입한 지 2개월이 지났는데, 긴 문서에서 맥락이 끊기는 문제가 자주 발생했다. 특히 API 명세서나 아키텍처 문서처럼 여러 섹션에 걸쳐 설명이 이어지는 경우 검색 결과가 불완전했다.

기존에는 256토큰 고정 크기로 청크를 나눴는데, 이게 문제였다.

실험 설계

청크 크기와 오버랩 비율을 조합해서 테스트했다.

  • 청크 크기: 256, 512, 1024 토큰
  • 오버랩: 0%, 25%, 50%
  • 평가 데이터: 실제 사용자 질의 100개
from langchain.text_splitter import RecursiveCharacterTextSplitter

def create_chunks(text, chunk_size, overlap_ratio):
    overlap = int(chunk_size * overlap_ratio)
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=overlap,
        separators=["\n\n", "\n", ".", " ", ""]
    )
    return splitter.split_text(text)

# 벡터 DB에 저장
for doc in documents:
    chunks = create_chunks(doc.content, 512, 0.25)
    embeddings = embedding_model.embed(chunks)
    vector_store.add(chunks, embeddings, metadata=doc.metadata)

결과

512토큰 + 25% 오버랩이 최적이었다.

  • 256토큰: 맥락 단절 빈번, 검색 정확도 72%
  • 512토큰 + 25% 오버랩: 검색 정확도 89%, 응답 품질 가장 우수
  • 1024토큰: 노이즈 증가, 검색 정확도 81%

오버랩이 없으면 문장이 중간에 잘리는 문제가 있었고, 50% 오버랩은 저장 공간 대비 효과가 미미했다.

추가 개선

의미 단위로 자르기 위해 separator 순서를 조정했다. 마크다운 헤더(##)를 최우선으로 두고, 그 다음 빈 줄, 마침표 순으로 설정하니 문서 구조를 더 잘 보존할 수 있었다.

separators=["\n## ", "\n### ", "\n\n", "\n", ". ", " "]

레이턴시는 256토큰 대비 15% 증가했지만, 검색 품질 개선이 더 중요하다고 판단해서 프로덕션에 적용했다.