[RAG] Chunking 전략이 답변의 질을 결정한다: 의미 단위 분할의 기술

RAG 시스템을 운영하다 보면 생각보다 단순한 부분에서 품질이 갈립니다. 대부분 모델이나 벡터 검색을 먼저 의심하지만 실제로는 문서를 어떻게 자르느냐가 더 중요하더군요. 바로 Chunking 전략입니다. 운영에서 여러 번 삽질해보니 답변 품질의 상당 부분이 여기서 결정됩니다.

RAG 시스템에서 Chunking 전략이 왜 중요한가

Chunking 전략은 RAG 시스템에서 문서를 어떤 단위로 나누어 벡터 인덱스에 저장할 것인지 결정하는 방식입니다. 처음 RAG를 만들 때 대부분 문서를 일정 길이로 잘라서 저장합니다. 저희도 처음에는 단순히 500 토큰 기준으로 문서를 나누었습니다.

초기에는 큰 문제가 없어 보였습니다. 문서 수는 약 200만 개였고 Elasticsearch 벡터 검색 latency는 평균 90ms 정도였습니다. RPS는 약 80 정도였습니다.

그런데 서비스가 커지면서 이상한 현상이 나타났습니다. 분명 문서에 답이 있는데도 모델이 제대로 답하지 못하는 경우가 꽤 많았습니다. 로그 분석을 해보니 검색된 문서에 핵심 정보가 반만 들어 있는 경우가 많았습니다.

 

잘못된 Chunking 전략이 만든 운영 Pain Point

당시 문서 인덱스는 약 500만 개의 chunk로 구성되어 있었습니다. 평균 chunk 길이는 약 450 토큰 정도였습니다.

문제는 문서 의미 단위와 chunk 경계가 맞지 않는다는 점이었습니다. 예를 들어 설명 문장이 두 개의 chunk로 나뉘어 저장되는 경우가 많았습니다.

실제 로그를 분석해보니 질문에 대한 핵심 정보가 두 개의 chunk에 나뉘어 있는 경우가 약 23퍼센트 정도였습니다. 이 경우 모델이 충분한 컨텍스트를 받지 못합니다.

특히 매뉴얼 문서나 기술 문서에서 이런 문제가 많이 발생했습니다. 문단이 길기 때문입니다.

 

초기 Chunking 구조

처음에는 가장 단순한 방식으로 구현했습니다. 일정 토큰 길이로 문서를 나누는 방식입니다.


# simple chunking example

chunk_size = 500
chunks = split_by_tokens(document, chunk_size)

이 방식의 장점은 구현이 매우 쉽다는 점입니다. 하지만 의미 단위가 고려되지 않습니다. 운영 환경에서는 이게 꽤 큰 문제로 이어집니다.

 

Chunking 전략을 다시 설계하게 된 의사결정 과정

이 문제를 해결하기 위해 내부에서 두 가지 접근 방법을 고민했습니다. 검색팀과 플랫폼팀 사이에서도 꽤 논쟁이 있었습니다.

방법 A 단순 chunk 크기 증가

첫 번째 방법은 단순히 chunk 크기를 늘리는 것이었습니다. 500 토큰 대신 1000 토큰으로 늘리는 방식입니다.

장점은 구현이 매우 간단합니다. 기존 코드 수정이 거의 필요 없습니다.

단점도 분명합니다. chunk가 커지면 검색 정확도가 떨어질 수 있습니다. 관련 없는 내용도 함께 들어가기 때문입니다.

 

방법 B 의미 기반 Chunking

두 번째 방법은 문단과 의미 단위를 기준으로 문서를 나누는 방식입니다. 문장 구조를 고려해서 chunk를 만드는 방식입니다.

장점은 문맥이 유지된다는 점입니다. 모델이 더 완전한 정보를 받을 수 있습니다.

단점은 구현이 조금 복잡합니다. 문단 분석 로직이 필요합니다. 인덱싱 파이프라인도 수정해야 합니다.

결국 저희는 의미 기반 Chunking 전략을 선택했습니다. 

 

의미 단위 Chunking 실제 구현 방법

최종적으로 선택한 방식은 세 단계 chunking 전략입니다. 먼저 문단 기준으로 나누고 그 다음 길이를 조정하는 방식입니다.

문단 기반 분할

첫 번째 단계는 문단 기준으로 문서를 나누는 것입니다. 대부분의 문서는 문단 단위로 의미가 구분됩니다.


List<String> paragraphs = document.split("\n\n");

이 방식만 적용해도 품질이 꽤 좋아졌습니다. 문단 단위로 의미가 유지되기 때문입니다.

 

Sliding Window Chunking

두 번째 단계는 sliding window 방식입니다. 문맥이 끊기지 않도록 일부 내용을 겹치게 만듭니다.


chunk_size = 400
overlap = 100

chunks = sliding_window(text, chunk_size, overlap)

이 overlap 전략이 꽤 중요합니다. 문장이 chunk 경계에서 잘리는 문제를 줄여줍니다.

 

최종 Chunk 생성 로직

실제 인덱싱 파이프라인에서는 다음과 같은 구조로 구현했습니다.


public List<String> createChunks(String document) {

    List<String> paragraphs = splitParagraph(document);

    List<String> chunks = new ArrayList<>();

    for (String p : paragraphs) {

        chunks.addAll(
            slidingWindowChunk(p, 400, 100)
        );

    }

    return chunks;
}

이 방식으로 인덱스를 다시 만들었더니 chunk 수는 약 20퍼센트 정도 증가했습니다. 하지만 검색 품질은 눈에 띄게 좋아졌습니다.

 

Chunking 전략 변경 이후 실제 성능 변화

Chunking 전략을 변경한 뒤 검색 정확도가 꽤 개선되었습니다. 내부 평가 기준으로 정답 문서가 검색되는 비율이 약 68퍼센트에서 84퍼센트 수준으로 올라갔습니다.

Latency는 약간 증가했습니다. chunk 수가 늘었기 때문입니다. 평균 검색 latency가 90ms에서 약 110ms 정도로 증가했습니다.

하지만 전체 RAG 응답 품질은 훨씬 좋아졌습니다. 특히 긴 설명이 필요한 질문에서 차이가 컸습니다.

 

Chunking 전략에 대한 현실적인 결론

RAG 시스템에서 Chunking 전략은 생각보다 중요합니다. 많은 팀이 모델이나 프롬프트에 집중하지만 실제로는 문서 구조가 더 큰 영향을 줍니다.

다만 완벽한 chunk 크기는 없습니다. 문서 종류에 따라 최적값이 달라집니다. 매뉴얼 문서와 FAQ 문서는 전략이 다를 수 있습니다.

그래서 운영 환경에서는 로그 분석을 통해 계속 튜닝해야 합니다. chunk 크기와 overlap 값은 서비스에 맞게 조정해야 합니다.

RAG 품질 문제는 대부분 작은 설계에서 시작됩니다. Chunking 전략도 그 중 하나입니다.