TestForge Blog
← 전체 포스트

RAG 개발 2편 - Chunking과 Embedding 전략, 검색 품질의 기초

RAG에서 청킹과 임베딩은 검색 품질의 핵심입니다. chunk 크기, overlap, 제목 보존, 코드 블록 처리, 임베딩 모델 선택과 인덱싱 전략까지 실무 기준으로 깊게 설명합니다.

TestForge Team ·

왜 청킹이 중요한가

RAG 품질이 낮을 때 많은 팀이 먼저 모델을 바꾸려 합니다. 하지만 실제로는 청킹 전략 때문에 성능이 흔들리는 경우가 더 많습니다.

청킹이 잘못되면 생기는 문제:

  • 관련 문맥이 둘로 갈라져 검색 실패
  • 검색은 되지만 필요한 문장이 빠짐
  • 너무 큰 chunk 때문에 불필요한 정보가 섞임
  • 동일 문서에서 비슷한 chunk가 과도하게 반환됨

청킹은 “텍스트를 자르는 작업”이 아니라 “검색 가능한 의미 단위를 만드는 작업”입니다.

chunk 크기를 어떻게 정할까

정답은 없지만, 대략적인 시작점은 있습니다.

일반 문서형 콘텐츠

  • 300~700 토큰
  • overlap 10~20%

FAQ / 짧은 정책 문서

  • 150~300 토큰
  • overlap 최소화

긴 운영 가이드 / 매뉴얼

  • 500~900 토큰
  • 섹션 단위 우선 분리

코드/설정 중심 문서

  • 코드 블록과 설명을 같이 묶기
  • 줄 수 또는 AST 기준 보조 고려

중요한 건 토큰 수 자체보다 “하나의 질문에 답할 수 있는 최소 문맥”이 유지되는지입니다.

절대 피해야 할 청킹 방식

고정 글자 수 단순 분할

def bad_chunk(text: str, size: int = 1000):
    return [text[i:i+size] for i in range(0, len(text), size)]

이 방식은 제목과 본문이 분리되고 문장 중간이 끊기기 쉽습니다.

제목 구조를 무시한 분할

문서 헤더가 중요한 기술 문서는 ## 설치, ## 인증, ## 장애 대응 같은 섹션 단위가 의미를 가집니다. 이 구조가 사라지면 검색 결과 설명력이 약해집니다.

권장하는 청킹 순서

실무에서는 보통 아래 순서를 추천합니다.

  1. 문서 구조 파싱
  2. 섹션 단위 분리
  3. 너무 긴 섹션만 세부 분할
  4. overlap 추가
  5. 제목/메타데이터 상속

예:

def build_chunk(title: str, section_title: str, body: str) -> str:
    return f"# {title}\n## {section_title}\n{body}"

이렇게 제목을 함께 넣어야 chunk 하나만 봐도 의미를 이해할 수 있습니다.

overlap은 왜 필요한가

문장이 chunk 경계에서 잘리는 문제를 줄이기 위해 overlap을 둡니다.

예:

Chunk A
- access token 만료 시 401 반환
- refresh token이 유효하면 재발급 가능

Chunk B
- refresh token이 유효하면 재발급 가능
- 재발급 후 새 access token 반환

이 정도의 중첩이 있어야 검색기가 중요한 문장을 놓치지 않습니다.

단, overlap을 너무 크게 잡으면 인덱스 중복과 검색 편향이 심해집니다.

코드 블록과 표는 특별 취급이 필요하다

기술 문서는 코드와 표가 핵심인 경우가 많습니다.

코드 블록

  • 코드만 따로 떼지 말고 설명과 묶기
  • 함수/설정 블록 단위 유지
  • 중간 줄에서 끊지 않기

  • 셀 구조가 깨지지 않게 텍스트 변환
  • 열 이름을 유지
  • 필요 시 표를 문장형 요약으로 보조 저장

예를 들어 비용 표나 오류 코드 표는 검색 쿼리와 직접 연결되는 경우가 많습니다.

임베딩 모델은 어떻게 고를까

임베딩 모델 선택 기준:

  • 한국어 성능
  • 도메인 적합도
  • 비용
  • latency
  • 벡터 차원 수
  • 재색인 비용

처음부터 최고 점수 모델을 고르기보다, 재현 가능한 기준으로 비교 실험하는 편이 낫습니다.

비교 시 보는 항목:

  • 같은 질문에서 top-k 결과 품질
  • 유사 문서와 비유사 문서 분리도
  • 코드/에러 메시지/약어 검색 성능

chunk metadata도 같이 저장하자

검색 후 필터링과 출처 표시를 위해 chunk 단위 메타데이터가 필요합니다.

예:

{
  "doc_id": "auth-guide",
  "chunk_id": "auth-guide-12",
  "title": "인증 API 가이드",
  "section": "토큰 재발급",
  "language": "ko",
  "updated_at": "2026-04-17T12:00:00Z"
}

문서 메타데이터만 저장하면 검색 후 어떤 섹션에서 왔는지 설명하기 어렵습니다.

인덱싱 전략

벡터 DB에는 보통 아래 단위로 저장합니다.

  • id
  • text
  • embedding
  • metadata

예시:

record = {
    "id": "auth-guide-12",
    "text": chunk_text,
    "embedding": embedding,
    "metadata": metadata,
}

실무에서는 재색인을 고려해 doc_id, chunk_id, content_hash, embedding_version을 같이 저장하는 편이 좋습니다.

문서 유형별 추천 청킹 전략

API 문서

  • 엔드포인트 단위
  • 요청/응답 예시 포함
  • 오류 코드 섹션 별도 유지

장애 대응 문서

  • 증상
  • 원인
  • 진단 절차
  • 복구 절차

이 순서를 하나의 의미 단위로 유지하는 편이 좋습니다.

제품 FAQ

  • 질문 1개 + 답변 1개 단위
  • 필요 시 관련 링크 포함

코드 문서

  • 함수/클래스 설명과 코드 예시 묶기
  • import만 따로 chunk하지 않기

자주 하는 실수

chunk를 너무 잘게 나누는 경우

질문에 필요한 문맥이 사라집니다.

chunk를 너무 크게 만드는 경우

검색은 되더라도 LLM에 넣을 때 노이즈가 같이 들어갑니다.

제목을 chunk에 넣지 않는 경우

본문만 보면 문맥을 이해하기 어려워집니다.

임베딩 버전을 관리하지 않는 경우

모델을 바꿨는데 예전 벡터와 섞여 있으면 검색 품질이 불안정해집니다.

추천 실험 방법

청킹 전략은 감으로 정하지 말고 평가셋으로 비교해야 합니다.

비교 실험 예:

  • 300 토큰 / overlap 30
  • 500 토큰 / overlap 50
  • 섹션 기반 청킹
  • 섹션 + 제목 보강 청킹

각 전략별로 top-3, top-5 검색 결과를 비교하면 어느 방식이 실제 질문에 강한지 드러납니다.

마무리

RAG에서 청킹과 임베딩은 검색 성능의 바닥을 결정합니다.

좋은 전략은 화려하지 않습니다.

  • 문서 구조를 보존하고
  • 너무 작지도 크지도 않게 나누고
  • 제목과 메타데이터를 유지하고
  • 평가셋으로 계속 비교합니다

다음 글에서는 이렇게 만들어진 chunk를 실제로 어떻게 검색하고, BM25와 벡터 검색을 어떻게 결합하며, reranking으로 결과를 다듬는지 살펴보겠습니다.