이 글은 PyCon Korea 2025에서 진행된 <RAG 애플리케이션 개발을 위한 Chunking 최적화> 세션을 정리한 내용입니다. RAG의 핵심 요소인 청킹의 중요성과 전략, 현실적인 최적화 기법 등을 소개합니다. 발표 자료는 PyCon Korea 2025 공식 홈페이지에서 확인할 수 있으며, 추후 파이콘 한국 유튜브 채널을 통해 영상으로도 만나보실 수 있습니다.
RAG 애플리케이션 개발을 위한 Chunking 최적화
강성우 개발자
안녕하세요. PyCon Korea 2025에서 발표한 'RAG 애플리케이션 개발을 위한 Chunking 최적화' 세션을 기반으로, RAG의 핵심 요소인 '청킹(Chunking)'에 대해 깊이 있게 다루어 보고자 합니다. RAG 애플리케이션 개발에 관심이 있거나, LLM의 한계를 넘어 더 정확한 답변을 제공하는 방법을 고민하는 분들을 위해 청킹의 중요성부터 다양한 전략, 그리고 현실적인 최적화 기법까지 다뤄보겠습니다. 천천히 읽어보시고 RAG 성능 개선을 위한 인사이트를 얻어가시길 바랍니다.

우리가 LLM과 대화할 때, 모델이 이전 내용을 '기억'하는 것처럼 보이지만 이는 사실과 다릅니다. 대부분의 LLM, 특히 트랜스포머 아키텍처 기반의 모델은 상태를 저장하지 않는(stateless) 구조입니다. 즉, 매번 새로운 질문을 받을 때마다 이전의 모든 대화 기록을 하나의 긴 '문맥(Context)'으로 묶어 함께 입력받아 처리할 뿐, 자체적인 기억 장치를 가지고 있지 않습니다.
이것이 바로 ChatGPT 같은 서비스에 '최대 대화 길이' 또는 'Context Window'라는 한계가 존재하는 이유입니다. 이 한계를 넘어서는 순간, 모델은 가장 오래된 대화부터 순차적으로 잊어버리게 됩니다. 결국 LLM의 기억을 효과적으로 관리하고, 필요한 정보를 적시에 제공할 책임은 모델이 아닌 그것을 활용하는 개발자에게 있습니다.

위 화면처럼 대화가 길어지면 LLM은 한계에 도달했다는 메시지를 보여주며, 이는 LLM이 내장된 기억 장치 없이 외부에서 주입된 문맥에만 의존한다는 사실을 명확히 보여줍니다.

그렇다면 LLM의 제한된 Context 길이보다 훨씬 더 방대한 최신 논문, 법률 문서, 기업 내부 보고서 등을 기반으로 질문에 답해야 한다면 어떻게 해야 할까요? 이 문제를 해결하기 위해 등장한 기술이 바로 RAG, 즉 '검색 증강 생성(Retrieval-Augmented Generation)'입니다.
RAG가 어떻게 질문과 관련된 정보 조각을 찾아내는지 이해하려면, 먼저 임베딩(Embedding)이라는 핵심 원리를 알아야 합니다. 임베딩은 텍스트(문장, 청크 등)를 컴퓨터가 이해할 수 있는 숫자의 배열, 즉 벡터(Vector)로 변환하는 과정입니다. 이 벡터는 다차원 공간의 한 점으로 표현되며, 의미가 비슷한 텍스트일수록 공간상에서 서로 가까운 위치에 자리하게 됩니다.예를 들어, '공주'라는 단어와 '왕자'라는 단어는 '왕족', '젊음', '궁전' 등 많은 의미를 공유하기 때문에 벡터 공간에서 서로 매우 가까운 이웃으로 위치합니다. 반면, '자동차'라는 단어는 의미적 연관성이 전혀 없으므로 이들과 아주 먼 곳에 위치하게 됩니다.
RAG 시스템은 먼저 문서의 모든 청크를 임베딩하여 벡터 데이터베이스에 저장해 둡니다. 그 후 사용자의 질문이 들어오면, 질문 또한 같은 방식으로 벡터로 변환합니다. 가령 사용자가 "왕자는 어디에 사나요?"라고 질문하면, 이 질문 벡터는 "공주와 왕자는 궁전에 살았습니다."라는 내용의 청크 벡터와 매우 가깝다고 계산됩니다. 그리고 이 질문 벡터와 데이터베이스에 저장된 수많은 청크 벡터들 간의 거리를 계산하여, 가장 가까운, 즉 의미적으로 가장 유사한 청크들을 찾아내는 것입니다.
RAG의 전체 프로세스는 바로 이 임베딩을 위한 준비 과정인 문서를 잘게 쪼개는 '청킹(Chunking)'에서 시작됩니다. 청킹은 긴 원본 문서를 관리하기 쉬운 의미 있는 단위의 조각(청크)으로 나누는 과정입니다. 이렇게 생성된 수많은 청크 중에서, 임베딩을 통해 사용자의 질문과 가장 관련성이 높은 청크 몇 개를 '검색(Search)'하여 찾아냅니다. 마지막으로, 찾아낸 청크의 내용을 사용자의 질문과 함께 LLM의 Context에 넣어주어, LLM이 이 정보를 바탕으로 답변을 '증강(Augment)'하여 생성하도록 만드는 것입니다.

위 그림처럼 RAG는 사용자의 질문과 가장 적합한 정보 조각을 먼저 찾아내는 방식으로 동작합니다. 이를 통해 LLM은 Context 길이의 물리적 한계를 극복하고, 마치 방대한 문서를 모두 이해한 것처럼 정확한 답변을 제공할 수 있게 됩니다.
앞서 설명했듯이, RAG의 검색 성능은 '얼마나 좋은 청크를 만드는가'에 달려있습니다. 좋은 청크란 그 자체로 의미가 명확하여, 임베딩되었을 때 고유한 의미 공간을 잘 대표할 수 있는 조각을 말합니다.
이때 중요한 것은, 청크가 원본 문서에서 떨어져 나와 독립적인 조각이 될수록 본래의 풍부한 문맥을 잃어버린다는 점입니다. 문맥을 잃은 청크는 그 의미가 모호해지고, 결국 임베딩 벡터 또한 그 청크의 핵심 의미를 정확하게 표현하지 못하게 됩니다. 이는 결국 검색 성능 저하의 직접적인 원인이 됩니다.
가장 간단하고 직관적인 청킹 방법은 문서를 단순히 고정된 글자 수나 토큰 수(예: 512 토큰)로 자르는 고정 크기 청킹입니다. 별도의 복잡한 로직 없이 구현이 가능해 많은 경우 기본값으로 사용되지만, 청크 크기를 어떻게 설정하느냐에 따라 명확한 장단점(Trade-off)이 발생합니다.

먼저 작은 크기(예: 128 토큰)로 청킹을 하면, 각 청크는 매우 구체적이고 집중된 정보만을 담게 되어 의미적 정확성이 높아집니다. 불필요한 내용이 적어 '신호 대 잡음비'가 높기 때문에, 사용자의 특정 질문에 대해 매우 정확한 검색 결과를 반환할 가능성이 큽니다. 하지만 이러한 장점은 심각한 문맥 정보 손실이라는 단점을 동반합니다. 하나의 완전한 아이디어가 여러 청크에 걸쳐 나뉘는 '문맥 파편화'가 발생하기 때문이죠.
예를 들어, "이러한 변화로 인해 매출이 15% 성장했다"는 청크를 찾아도, '이러한 변화'가 무엇을 의미하는지는 다른 청크에 담겨 있어 LLM이 불완전한 정보를 받게 되는 치명적인 문제가 발생할 수 있습니다.
반대로 큰 크기(예: 1024 토큰)로 청킹을 하면, 하나의 아이디어를 설명하는 데 필요한 배경, 핵심, 결론이 하나의 청크에 모두 포함될 가능성이 커져 풍부한 문맥을 보존하는 데 유리합니다. 이는 LLM이 더 깊이 있는 답변을 생성하도록 돕습니다. 그러나 이 방식은 검색 정확도를 저하하는 단점이 있습니다. 하나의 청크에 여러 하위 주제가 섞여 있으면, 해당 청크의 벡터 임베딩이 모든 주제의 '평균'적인 의미를 갖게 되어 검색 결과의 순위가 밀릴 수 있습니다. 즉, 사용자의 구체적인 질문과 관련 없는 '잡음'이 많아져 핵심 정보가 희석되는 것입니다.
고정 크기 청킹의 고질적인 문맥 파편화 문제를 해결하기 위해, 보다 정교한 규칙 기반 방식인 '재귀적 청킹(Recursive Chunking)'이 사용됩니다. 이 방법은 임의의 글자 수가 아닌, 문단의 끝(\n\n), 문장의 끝(.) 등 의미가 구분되는 지점을 기준으로 문서를 자르려고 시도합니다.

위 그림처럼 우선순위가 높은 구분자(문단)부터 시작해 점차 작은 단위(문장)로 쪼개나가는 것을 볼 수 있습니다. 먼저 가장 큰 단위인 문단으로 문서를 분할하고, 나뉜 청크가 여전히 설정된 크기보다 크면 다음 우선순위인 문장 단위로 재귀적으로 분할하여, 문맥 파편화를 최소화하면서도 청크의 크기를 적절하게 유지하려는 균형점을 찾습니다.
'시맨틱 청킹'은 이름처럼 '의미'를 기반으로 문서를 분할하려는 더 발전된 시도입니다. 이 방식은 먼저 문서를 개별 문장 단위로 모두 분리합니다. 그 후, 각 문장을 임베딩하여 의미 공간의 벡터로 변환하고, 인접한 문장들 간의 벡터 유사도를 계산합니다. 만약 두 문장의 의미가 유사하여 유사도 점수가 특정 임계값을 넘으면, 이들을 하나의 청크로 병합하는 과정을 반복합니다.

위 그림은 문장들을 의미적으로 가까운 단위로 묶어, 청크를 구성하는 개념을 보여줍니다. 하지만 이 방법은 진정한 의미론적 이해라기보다, 벡터 유사도라는 수학적 근접성에 의존하는 피상적인 분할이라는 한계를 가집니다.
최근에는 진정한 의미 기반 청킹을 위해 'MoC(Mixture-of-Chunkers)'라는 정교한 기법이 제안되었습니다. MoC는 마치 전문 분야별 컨설턴트 팀처럼 작동합니다. 법률 문서에 특화된 '법률 청킹 전문가', 의료 논문에 특화된 '의료 청킹 전문가' 등 여러 도메인별 '전문가 청커'를 미리 학습시켜 둡니다. 그리고 새로운 문서가 들어오면, '라우터'라는 관리자 모델이 문서의 내용을 분석하여 "이 문서는 법률 관련 내용이니, 법률 전문가가 처리하는 것이 가장 좋겠다!"라며 가장 적합한 전문가에게 청킹 작업을 위임하는 방식입니다.

위 그림처럼 MoC는 여러 전문가 모델과 라우터를 통해 매우 정교한 방식으로 동작합니다. 이는 매우 이상적인 방법이지만, 여러 개의 큰 모델을 유지하고 운영해야 하므로 비용이 매우 비싸다는 명확한 단점이 있습니다.
앞서 살펴보았듯이, 청킹 과정에서 청크가 원본 문서로부터 독립될수록 본래의 문맥을 잃어버리는 문제가 발생합니다. 이 문맥 손실은 청크의 의미를 모호하게 만들어 부정확한 임베딩 벡터를 생성하게 하고, 이는 RAG 시스템의 검색 성능을 하락시키는 핵심 원인이 됩니다. 따라서 좋은 청킹 전략이란, 단순히 문서를 나누는 것을 넘어 '어떻게 하면 각 청크가 문맥 정보를 최대한 유지하게 할 것인가?'를 고민하는 과정입니다.
검색 단계에서 발생하는 문제를 완화하기 위해, 청킹 과정에서부터 적용할 수 있는 다음과 같은 고도화 기법들이 있습니다.

출처를 필터링하는 방식으로 사용자가 "2025년 파이콘 참석자 수 알려줘"라고 질문했을 때, RAG 시스템이 '2024년 보고서'가 아닌 '2025년 보고서'의 청크를 정확히 찾아내도록 하려면 어떻게 해야 할까요? 정답은 메타데이터에 있습니다. 각 청크를 생성할 때, 내용뿐만 아니라 해당 청크의 출처(예: 2025년도 보고서.pdf, 3페이지)와 같은 문맥 정보를 메타데이터로 함께 저장하는 것입니다.

위 예시처럼 '2025년'이라는 메타데이터 덕분에 LLM은 정확한 문서를 참조하여 답변할 수 있게 됩니다. 이 메타데이터는 임베딩 과정에 포함되어 각 청크의 고유한 구분력을 높여주며, 검색 시 필터링 조건으로 사용되어 검색 정확도를 극적으로 향상시킵니다.
청크 오버랩(Overlap)과 메타데이터를 활용한 병합 청킹 시 문맥 손실을 줄이는 또 다른 유용한 전략은 '청크 오버랩(Chunk Overlap)'입니다. 이는 각 청크가 이전 청크의 끝부분 일부를 포함하도록 의도적으로 중복된 내용을 만드는 기법입니다. 오버랩은 문장의 중간이 잘리는 것을 방지해 문맥을 보존하고, 청크 경계에 있는 정보에 대한 검색 안정성을 높이는 장점이 있습니다.
하지만 이 오버랩 전략은 검색 이후에 심각한 단점을 야기합니다. 만약 검색 결과로 이웃한 두 개의 오버랩된 청크가 모두 반환되었을 때, 이들을 단순히 이어 붙이면 내용이 중복되어 깨지는 문제가 발생합니다. 예를 들어, 아래와 같이 두 개의 청크가 검색되었다고 가정해 보겠습니다.
이 둘을 그대로 합치면 아래와 같은 구문 오류(Syntax Error)를 일으키는 코드가 만들어집니다. Python에서 리스트 컴프리헨션은 [x*2 for x in range(10)]for x in range(10)] 형태로 작성합니다.
오버랩된 두 코드 조각을 단순히 합치면 구문 오류가 발생합니다. 바로 이 문제를 해결하는 열쇠가 메타데이터입니다.

청킹을 할 때 '겹치는 부분'의 정확한 위치나 내용을 메타데이터로 함께 저장해두면, 나중에 두 청크를 병합할 때 이 정보를 바탕으로 중복되는 부분을 완벽하게 제거하고 하나의 올바른 문장으로 복원할 수 있습니다.
이는 정보의 무결성을 유지하는 데 매우 중요한 역할을 합니다.
"효율성이 30% 향상되었다"라는 청크만으로는 이것이 '이미지 모델의 성능'에 대한 것인지, '데이터 처리 속도'에 대한 것인지 알 수 없습니다. '문맥 기반 검색'은 이처럼 문맥을 잃어버린 청크의 독립성 문제를 해결하기 위해, 청킹 후에 각 청크마다 "이 청크는 문서의 3.1절에 나오는 이미지 모델의 성능 평가에 관한 내용이다"와 같이 그 청크의 주변 문맥을 요약한 짧은 문장을 인위적으로 추가해 주는 기법입니다.

위 그림처럼 각 청크에 해당 문맥을 요약한 정보를 미리 추가해 두는 것이 이 방식의 핵심입니다. 이 '문맥 주입' 과정을 통해 각 청크는 독립적으로도 충분한 정보를 가지게 되어 검색 성능이 향상됩니다.
'LATE CHUNKING'은 이름 그대로 '나중에 청킹한다'는 개념의 접근법으로, 청크의 독립성 문제를 보다 근본적으로 해결하려 합니다. 기존 방식이 문서를 먼저 청킹하고 각 청크를 개별적으로 임베딩하는 것과 달리, 이 방법은 긴 문서 전체를 먼저 통째로 임베딩하여 문서의 전역적인 문맥을 담은 '전체 문서 임베딩'을 생성합니다. 그 후, 문서를 청킹하고 개별 청크를 임베딩할 때, 이 '전체 문서 임베딩' 값을 각 청크의 임베딩에 주입해 줍니다.

위 그림처럼 LATE CHUNKING은 문서 전체의 정보를 먼저 임베딩하여 각 청크에 주입하는 차이가 있습니다. 예를 들어, "베를린은 독일의 수도이다"라는 내용의 문서에서 "그 도시는 또한..."이라는 문장이 포함된 청크가 있다면, LATE CHUNKING을 통해 이 청크의 임베딩은 '베를린'이라는 전역적 문맥 정보를 품게 됩니다. 덕분에 검색 시 훨씬 더 정확하게 의도를 파악할 수 있게 됩니다.
지금까지 RAG 애플리케이션의 성능을 좌우하는 핵심 요소인 청킹과정에 대해 살펴보았습니다. 효과적인 RAG 시스템을 구축하기 위해서는 단순히 문서를 나누는 초기 단계를 넘어, 정보 손실을 최소화하고 원본의 풍부한 문맥을 최대한 보존하려는 고도화된 전략이 필수적입니다.
어떤 청킹 방식이 절대적으로 우월하다고 말하기는 어렵습니다. 다루려는 문서의 종류(정형, 비정형), 도메인의 특성, 그리고 사용자의 주요 질문 패턴에 따라 최적의 전략은 달라지기 때문입니다. 이 글에서 소개된 다양한 기법들을 바탕으로 여러분의 RAG 프로젝트에 가장 적합한 청킹 전략을 찾고, 실험하며, 발전시켜 나가는 데 도움이 되길 바랍니다.
©️요즘IT의 모든 콘텐츠는 저작권법의 보호를 받는 바, 무단 전재와 복사, 배포 등을 금합니다.