본문 바로가기
claudecode.to
문서 목록
RAG와 검색

RAG 청크 사이즈와 분할 전략 4가지

RAG 청크 사이즈를 정답값이 아니라 우리 문서로 재서 고르는 방법을 정리합니다. 고정 길이·문장·제목 구조·의미 기반 네 가지 분할 전략의 차이와 오버랩, 한국어 길이 세는 법까지 다룹니다.

18분2026-09-20 갱신

청크는 긴 문서를 검색과 생성에 쓰기 좋은 크기로 자른 조각입니다. RAG는 질문과 관련된 사내 문서를 찾아 그 내용을 모델에 함께 넘겨 답하게 하는 구조인데, 이때 찾고 넘기는 단위가 문서 전체가 아니라 청크입니다. 이 글은 청크 사이즈를 정답값으로 외우는 대신 우리 문서로 재서 고르는 방법, 그리고 분할 방식 네 가지가 각각 어떤 문서에서 깨지는지를 다룹니다.

핵심 요약

  • 청크는 검색 단위이면서 동시에 생성 입력입니다. 한쪽만 보고 크기를 정하면 반대쪽이 나빠집니다.
  • 분할 방식은 고정 길이, 문장·문단, 제목 구조, 의미 기반 네 가지로 나뉘고, 각각 깨지는 문서 유형이 다릅니다.
  • 오버랩은 경계에 걸린 내용을 살리는 장치지만 저장 용량과 결과 중복이라는 대가가 따릅니다.
  • 표·코드·목록은 잘리는 순간 의미가 사라지므로 일반 문장과 다른 규칙이 필요합니다.
  • 길이는 글자 수로 설계하고 토큰 수로 검증합니다. 출처와 제목 경로를 메타데이터로 함께 저장합니다.

청크가 검색 단위이자 생성 입력이라는 말의 뜻

검색 엔진은 문서 하나에 점수를 매기는 것이 아니라 청크 하나하나에 점수를 매깁니다. 청크가 크면 그 안에 관련 없는 내용이 섞여 질문과의 관련성이 희석되고, 점수가 낮아져 상위에 오르지 못합니다. 이 관점만 보면 청크는 작을수록 좋아 보입니다.

그런데 검색에 걸린 청크의 본문은 그대로 모델에게 넘어갑니다. 청크가 너무 작으면 답에 필요한 앞뒤 설명이 잘려 나가 모델이 조건을 빼먹거나 반대로 해석합니다. "예외적으로 부서장 승인이 있으면"이라는 단서가 다른 조각으로 넘어가 버리는 식입니다. 이 관점에서는 청크가 클수록 좋습니다.

두 요구가 정반대라서 청크 사이즈에는 보편적인 정답이 없습니다. 실무에서는 두 가지를 분리해 해결하기도 합니다. 검색은 작은 조각으로 하고, 모델에 넘길 때는 그 조각이 속한 더 큰 단위(문단 전체나 절 전체)를 함께 붙여 주는 방식입니다. 구현이 늘어나는 대신 양쪽 요구를 모두 챙길 수 있어, 청크 사이즈를 아무리 조정해도 답이 부실할 때 검토할 만한 선택지입니다.

분할 방식 4가지는 각각 어디서 깨지나요

방식자르는 기준강점깨지는 문서 유형
고정 길이글자 수를 세어 일정 길이마다구현이 가장 단순하고 조각 크기가 고르다규정·매뉴얼처럼 조항 번호와 본문이 떨어지면 안 되는 문서, 표가 든 문서
문장·문단 단위문장 끝이나 빈 줄에서문장이 잘리지 않아 읽히는 조각이 나온다한 문단이 지나치게 긴 회의록, 문장 부호가 불규칙한 옮겨 적은 글
제목 구조 기준마크다운 제목, 조·항·절 머리의미 단위가 그대로 보존되고 제목 경로를 메타데이터로 쓸 수 있다제목이 없거나 서식만으로 구분된 문서, 한 절이 지나치게 긴 문서
의미 기반인접 문장의 유사도가 끊기는 지점구조가 없는 글에서도 주제 경계를 찾는다계산 비용이 크고 결과가 불안정해 재현이 어려움, 문서가 많을수록 부담

사내 문서는 유형이 섞여 있습니다. FAQ 400건, 규정 120건, 제품 매뉴얼 300건, 회의록 200건이 한 코퍼스에 들어 있다면 한 방식으로 전부 처리하는 것이 오히려 부자연스럽습니다. FAQ는 질문과 답을 묶어 한 건을 통째로, 규정은 조·항 단위로, 회의록은 문단 단위로 자르는 식으로 유형마다 다른 규칙을 두는 구성이 잘 맞는 경우가 많습니다. 유형별 규칙은 적재 단계에서 문서 유형 메타데이터를 보고 갈라 주면 됩니다.

의미 기반 분할은 매력적으로 들리지만 우선순위가 높지 않습니다. 제목 구조가 살아 있는 문서가 대부분인 사내 환경에서는 제목 기준이 더 싸고 더 안정적입니다. 구조가 전혀 없는 옮겨 적은 글이 많을 때 검토합니다.

오버랩은 왜 두고 무엇을 내주나요

오버랩은 앞 조각의 끝부분을 다음 조각의 앞에 겹쳐 넣는 것입니다. 자르는 지점이 하필 설명의 한가운데를 지나면 어느 조각에도 답이 온전히 남지 않는데, 겹쳐 두면 적어도 한쪽에는 살아남습니다. 특히 고정 길이 분할에서는 사실상 필수에 가깝습니다.

대가는 세 가지입니다. 첫째, 같은 내용을 두 번 저장하므로 색인 크기와 임베딩 계산량이 겹친 비율만큼 늘어납니다. 둘째, 검색 결과에 거의 같은 내용의 조각이 나란히 올라와 모델에 넘기는 자리를 낭비합니다. 셋째, 평가할 때 같은 문서의 청크가 여러 개 잡혀 점수가 부풀려 보일 수 있습니다.

그래서 오버랩을 쓸 때는 검색 결과를 문서 ID 기준으로 묶어 중복을 걷어내는 처리를 함께 넣습니다. 평가도 청크 단위가 아니라 문서 단위로 모아서 판정해야 다른 분할 방식과 공정하게 비교됩니다. 겹치는 양은 조각 길이의 10~20% 범위에서 후보를 두 개쯤 잡아 재 보는 정도면 충분하고, 제목 구조 기준으로 자를 때는 경계가 의미 단위와 일치하므로 오버랩을 아예 빼도 되는 경우가 많습니다.

표·코드·목록이 잘리면 어떤 문제가 생기나요

표는 머리행과 값이 다른 조각으로 갈라지는 순간 숫자만 남은 의미 없는 덩어리가 됩니다. "1인당 한도"라는 머리행이 없으면 "300,000"이라는 값은 아무것도 말해 주지 않고, 검색에서도 걸리지 않으며, 모델이 받아도 엉뚱하게 해석합니다. 대응은 머리행을 각 조각의 앞에 반복해 붙이거나, 표 전체를 자르지 않고 한 청크로 두는 것입니다. 표가 지나치게 크면 행 단위로 펼쳐 "항목: 값" 형태의 문장으로 바꿔 색인하는 방법도 씁니다.

코드 블록과 오류 코드도 비슷합니다. 매뉴얼의 예제 코드가 중간에서 끊기면 나머지 절반은 실행할 수 없는 조각이 되고, E-1042 같은 식별자가 줄 경계에서 갈라지면 검색어와 맞지 않습니다. 코드 펜스는 분할 대상에서 제외하고 통째로 유지하는 규칙을 두는 편이 안전합니다.

번호 목록은 앞의 도입 문장과 떨어지면 무엇에 대한 목록인지 알 수 없게 됩니다. 목록이 길어 어쩔 수 없이 잘라야 한다면 도입 문장을 각 조각에 반복해 붙입니다.

# 자르면 안 되는 구간을 먼저 묶어 두고, 나머지만 길이 기준으로 나눈다
KEEP_WHOLE = ("```", "|")  # 코드 펜스 시작과 표 행

def split_blocks(lines):
    blocks, buf, in_code = [], [], False
    for line in lines:
        if line.startswith("```"):
            in_code = not in_code
        buf.append(line)
        if not in_code and line.strip() == "" and not line.startswith(KEEP_WHOLE):
            blocks.append("\n".join(buf))
            buf = []
    if buf:
        blocks.append("\n".join(buf))
    return blocks

한국어 문서에서 길이는 글자로 세나요 토큰으로 세나요

설계와 점검은 글자 수로 하는 편이 편합니다. 사람이 조각을 눈으로 확인할 때 기준이 되고, 문서 편집자와 대화할 때도 "800자 정도"가 "512토큰"보다 통합니다. 반면 모델에 넘길 양을 계산할 때는 토큰이 기준입니다. 넘기는 조각 수와 조각당 토큰 수를 곱한 값이 모델 입력 예산을 넘지 않아야 합니다.

문제는 한국어에서 글자 수와 토큰 수의 비율이 일정하지 않다는 점입니다. 토크나이저에 따라, 그리고 문서에 한자어·영문·숫자·기호가 얼마나 섞였는지에 따라 같은 800자가 꽤 다른 토큰 수가 됩니다. 실용적인 절차는 이렇습니다. 글자 수로 자르는 규칙을 정하고, 문서 유형별로 조각 30개쯤을 표본으로 뽑아 실제 토큰 수를 재고, 가장 긴 조각이 예산 안에 들어오는지 확인합니다. 예산을 넘는 유형이 있으면 그 유형의 글자 기준만 낮춥니다.

임베딩 모델에는 입력 길이 상한이 따로 있습니다. 상한을 넘는 조각은 조용히 뒤가 잘린 채 인코딩되어, 조각 뒷부분의 내용이 검색에서 영원히 안 걸리게 됩니다. 이 잘림은 오류를 내지 않아 알아채기 어려우므로, 적재 단계에서 상한 초과 조각의 개수를 세어 로그에 남겨 두는 편이 좋습니다.

청크 메타데이터는 왜 함께 넣나요

청크 본문만 저장하면 검색 결과로 떠오른 조각이 어느 문서의 어디인지 알 수 없습니다. 적재할 때 다음 정보를 함께 붙여 둡니다.

{
  "doc_id": "hr-rule-014",
  "chunk_no": 3,
  "title_path": "인사 규정 > 제3장 휴가 > 연차 유급휴가",
  "doc_type": "규정",
  "updated_at": "2026-07-01",
  "access_tag": "all",
  "text": "연차 유급휴가는 신청일 기준 3영업일 전까지 ..."
}

이 정보는 네 군데에서 일합니다. 첫째, 답에 출처를 달아 사용자가 원문을 확인할 수 있게 합니다. 출처가 없으면 사용자는 답을 믿을지 말지 판단할 방법이 없습니다. 둘째, 제목 경로를 청크 본문 앞에 붙여 색인하면 "연차"라는 단어가 본문에 없어도 제목으로 걸립니다. 셋째, 갱신일과 문서 유형으로 검색 범위를 좁히거나 최신본을 우선할 수 있습니다. 넷째, 열람 권한 태그가 있어야 권한 없는 사용자에게 제한 문서가 섞이는 사고를 검색 단계에서 막을 수 있습니다. 이런 설계를 팀의 공통 규칙으로 굳히는 과정은 에이전트 심화 과정에서 다룹니다.

문서 ID와 청크 번호를 조합해 저장 ID를 결정적으로 만들어 두면, 같은 문서를 두 번 적재해도 조각이 중복으로 쌓이지 않습니다. 문서가 삭제됐을 때 해당 조각들을 지우는 경로도 같은 ID 규칙 위에서 만들 수 있습니다.

정답 대신 우리 문서로 재서 고르는 절차

청크 사이즈를 정하는 일은 고르는 문제이지 맞히는 문제가 아닙니다. 절차는 다음과 같습니다.

  1. 후보를 서너 개로 줄입니다. 예를 들어 고정 800자에 오버랩 100자, 문단 단위, 제목 구조 기준 세 가지처럼 성격이 다른 것을 고릅니다. 열 가지를 비교하려 들면 끝나지 않습니다.
  2. 같은 판정 데이터와 같은 검색 구성으로 각 후보를 색인하고 Recall@k를 잽니다. 이때 바꾸는 것은 청크 방식 하나뿐이어야 합니다.
  3. 청크 단위 결과를 문서 ID로 묶어 중복을 제거한 뒤 채점합니다. 그래야 조각을 잘게 낸 방식이 유리해지는 착시를 피합니다.
  4. 지표가 비슷하면 답 품질을 함께 봅니다. 질의 열 개쯤에 대해 실제로 생성된 답을 읽고, 조건이나 예외가 빠진 곳이 있는지 확인합니다.
  5. 색인 크기와 적재 시간도 표에 적습니다. 품질 차이가 미미한데 색인이 두 배로 커지는 방식은 운영에서 부담이 됩니다.
  6. 고른 뒤에는 실패한 조각을 모아 둡니다. 표가 갈라진 사례, 조항 번호가 본문과 떨어진 사례를 목록으로 쌓아 두면 다음 개선의 재료가 됩니다.

재는 자를 만드는 방법과 실험 기록 양식은 RAG 성능 개선 순서에 정리돼 있습니다. 청크만 따로 최적화하는 것은 의미가 없고, 같은 판정 데이터 위에서 다른 변경들과 나란히 비교될 때만 판단이 섭니다.

정리

청크는 검색 단위이자 생성 입력이라는 두 얼굴을 가진 탓에 보편적인 정답 크기가 없습니다. 문서 유형마다 다른 분할 규칙을 두고, 표·코드·목록은 자르지 않는 예외를 만들고, 출처와 제목 경로를 메타데이터로 함께 저장하는 것이 안정적인 기본형입니다. 그 위에서 후보 서너 개를 우리 판정 데이터로 재서 고르면 됩니다.

무엇을 재고 어떻게 기록할지는 RAG 성능 개선 순서에서 이어집니다. 모델에 얼마나 넣을지 판단하는 감각은 컨텍스트 윈도우 관리에, 사내 지식을 모델에 붙이는 다른 방식은 도메인 지식과 컨텍스트에 정리돼 있습니다. 사내 문서 검색을 측정하고 고치는 루프를 팀 단위로 익히는 과정은 에이전트 심화 과정에서 다룹니다.

자주 묻는 질문

RAG청크청크 사이즈오버랩문서 구조