티스토리 뷰

백엔드 개발자를 위한 RAG 설계: 청킹, 임베딩, 벡터 검색 쉽게 이해하기RAG가 문서를 찾는 순서부터 청킹 크기, 임베딩 모델, HNSW 설정, 검색 품질 평가까지 실무에 필요한 내용만 쉽게 정리했습니다.

RAG는 LLM이 답변하기 전에 관련 문서를 찾아 주는 구조입니다. 사용자가 질문하면 사내 문서나 기술 문서에서 근거를 찾고, 그 내용을 LLM에 전달합니다. LLM은 전달받은 근거를 바탕으로 답변합니다.

처음에는 문서를 잘라 벡터 DB에 넣고 가까운 내용을 검색하면 됩니다. 하지만 실제 서비스에서는 다른 문제가 생깁니다. 표현만 조금 바꿔도 검색 결과가 달라지고, 제품 코드나 오류 번호를 놓치기도 합니다. 수정 전 문서가 계속 검색되거나 권한이 없는 문서가 결과에 포함될 수도 있습니다.

 

벡터 DB부터 고르기보다 검색 결과를 어떻게 평가할지 먼저 정해야 합니다.

청킹 크기나 임베딩 모델, HNSW 설정은 문서와 질문에 따라 결과가 달라집니다. 다른 서비스에서 잘 동작한 설정도 우리 데이터에서는 같은 결과가 나오지 않을 수 있습니다. 이 글에서는 실제 질문으로 검색 품질을 확인하고, 그 결과에 맞춰 설정을 조정하는 순서를 설명합니다.

 

 

먼저 알아둘 용어

  • 청크: 검색하기 좋게 나눈 문서 조각
  • 청킹: 문서를 청크로 나누는 작업
  • 임베딩: 문장이나 문서를 숫자 배열로 바꾸는 작업
  • 벡터 검색: 숫자로 바꾼 문장 사이의 거리를 계산해 의미가 가까운 내용을 찾는 검색
  • BM25: 제품 코드나 오류 번호처럼 정확한 단어를 잘 찾는 검색
  • 재정렬: 검색된 후보를 질문과 다시 비교해 순서를 바꾸는 작업
  • Recall@k: 관련 문서가 상위 k개 검색 결과에 포함된 비율
  • 색인: 문서를 빠르게 검색할 수 있도록 정리해 저장한 구조
  • 근사 검색: 모든 벡터를 비교하지 않고 가까울 가능성이 높은 벡터만 빠르게 찾는 검색

RAG는 두 단계로 나누면 이해하기 쉽습니다

RAG는 문서를 미리 저장하는 단계와 사용자의 질문을 처리하는 단계로 나눌 수 있습니다.

문서를 저장할 때

  1. PDF, 웹 문서, 코드 같은 원문을 가져옵니다.
  2. 제목과 문단 구조를 살려 문서를 나눕니다.
  3. 각 청크를 임베딩합니다.
  4. 벡터와 문서 정보를 검색 시스템에 저장합니다.

질문을 받을 때

  1. 사용자 질문과 접근 권한을 확인합니다.
  2. 벡터 검색과 BM25로 관련 문서를 찾습니다.
  3. 필요하면 검색 결과를 재정렬합니다.
  4. 상위 청크를 LLM에 전달합니다.
  5. 답변과 출처를 사용자에게 보여 줍니다.

문서 저장 단계와 질문 처리 단계를 분리하면 변경 작업이 쉬워집니다. 임베딩 모델을 바꿀 때 원문을 다시 수집할 필요가 없습니다. 문서가 수정됐을 때는 바뀐 문서만 다시 청킹하고 색인할 수 있습니다.

청크에는 아래 정보를 함께 저장하는 편이 좋습니다.

  • 원문 ID와 청크 ID
  • 문서 버전 또는 수정 시각
  • 제목과 소제목 위치
  • 출처 URL
  • 임베딩 모델과 버전
  • 사용자나 부서의 접근 권한

권한은 답변을 만든 뒤 확인하면 늦습니다. 검색 단계에서 권한이 없는 문서를 제외해야 로그와 재정렬 요청에도 민감한 내용이 들어가지 않습니다.

청킹은 문서의 의미를 지키는 작업입니다

청크가 너무 짧으면 질문에 답할 정보가 부족합니다. 너무 길면 서로 다른 주제가 한 청크에 섞여 검색 정확도가 떨어지고 LLM 비용도 늘어납니다.

예를 들어 이 기간에는 환불이 불가능합니다라는 문장만 저장하면 어떤 상품과 기간을 말하는지 알 수 없습니다. 표의 한 행만 저장하면 열 제목이 사라집니다. 코드도 메서드 본문만 잘라 놓으면 클래스 정보를 놓칠 수 있습니다.

문서 종류에 맞춰 나누는 것이 먼저입니다.

  • 일반 문서: 제목, 문단, 목록을 가능한 한 유지합니다.
  • API 문서: 엔드포인트별 요청과 응답 예제를 함께 둡니다.
  • 코드: 함수나 클래스 단위로 나눕니다.
  • 정책 문서: 조항 번호와 적용 대상을 함께 저장합니다.
  • 표: 제목과 열 이름이 데이터와 함께 검색되도록 만듭니다.

Azure AI Search는 512토큰과 25% 겹침을 초기 설정값으로 제안합니다. 모든 문서에 그대로 적용할 값은 아니므로 문서 종류별로 비교해야 합니다.

첫 실험은 아래 범위에서 시작할 수 있습니다.

  • 짧은 FAQ와 오류 메시지: 150에서 350토큰
  • 일반 기술 문서와 업무 문서: 400에서 800토큰
  • 정책과 매뉴얼: 조항이나 절 단위로 나눈 뒤 최대 길이 제한
  • 코드: 함수나 클래스 단위로 나누고 상위 클래스 이름을 함께 저장

청크를 겹치면 문장이 경계에서 잘리는 문제를 줄일 수 있습니다. 하지만 겹침이 많으면 같은 문장이 여러 번 검색됩니다. 저장 공간과 검색 비용도 늘어납니다. 먼저 문단 경계를 지키고 제목 정보를 붙인 뒤, 정보가 계속 잘릴 때만 겹침을 늘리는 편이 낫습니다.

Anthropic의 Contextual Retrieval은 각 청크에 문서 제목과 섹션 정보를 짧게 붙이는 방식입니다. Anthropic이 공개한 내부 실험에서는 검색 실패가 49% 줄었고 재정렬을 함께 사용했을 때 67% 줄었습니다. 공급사 내부 데이터로 측정한 결과이므로 같은 수치가 나온다고 볼 수는 없습니다. 다만 제목과 상위 문맥을 청크에 추가하는 방법은 직접 시험해 볼 만합니다.

임베딩 모델은 실제 질문으로 비교합니다

임베딩 모델은 문장을 숫자 좌표로 바꿉니다. 의미가 비슷한 문장은 가까운 위치에 놓이도록 학습됩니다. 그래서 표현이 달라도 의미가 비슷한 문서를 찾을 수 있습니다.

리더보드 점수만으로 모델을 고르기는 어렵습니다. MTEB 연구에서도 모든 작업에 가장 좋은 단일 모델은 없었습니다. 한국어 문장, 영문 API 이름, 상품 코드가 섞인 데이터는 일반 영어 문서와 검색 조건이 다릅니다.

관리형 API를 사용하면 모델 서버를 직접 운영하지 않아도 됩니다.

  • OpenAI text-embedding-3-small: 기본 1536차원
  • OpenAI text-embedding-3-large: 기본 3072차원, 차원 축소 지원
  • Gemini Embedding: 기본 3072차원, 768과 1536차원도 권장
  • Cohere embed-v4.0: 텍스트와 이미지가 섞인 문서 지원
  • Voyage 4: 문서와 질문의 입력 유형을 구분하고 여러 차원 지원

외부 전송이 어려운 문서라면 직접 운영하는 모델도 검토할 수 있습니다. BGE-M3는 벡터 검색과 키워드 검색을 함께 지원하며 100개가 넘는 언어를 다룬다고 공개되어 있습니다. 다만 GPU와 CPU 비용, 모델 서버 운영, 장애 대응까지 직접 맡아야 합니다.

모델은 아래 질문으로 비교합니다.

  • 띄어쓰기나 맞춤법이 틀린 한국어 질문
  • 한국어와 영문 API 경로가 섞인 질문
  • 상품 코드, 버전, 오류 번호를 찾는 질문
  • 사내 약어와 정식 명칭을 섞은 질문
  • 답이 없거나 접근 권한이 없는 질문

같은 문서와 질문으로 Recall@k, 검색 시간, 색인 크기, 비용을 비교하면 됩니다. 모델마다 문장을 숫자로 바꾸는 방식이 다르므로 임베딩 모델을 바꾸면 대부분 전체 문서를 다시 임베딩해야 합니다.

벡터 검색과 BM25를 함께 사용합니다

벡터 검색은 로그인이 안 돼요인증 실패 해결 방법처럼 표현이 달라도 의미가 비슷한 문장을 찾는 데 강합니다. 반면 ERR-1024, 상품 코드, API 경로처럼 글자가 정확히 일치해야 하는 검색에는 약할 수 있습니다.

BM25는 정확한 단어를 찾는 데 강합니다. 두 검색을 함께 사용하면 의미 검색과 키워드 검색의 장점을 모두 활용할 수 있습니다.

  1. 같은 질문을 벡터 검색과 BM25에 보냅니다.
  2. 두 검색에서 후보 문서를 가져옵니다.
  3. 각 검색 결과의 순위를 기준으로 합치는 RRF를 적용합니다.
  4. 재정렬 모델이 질문과 후보를 다시 읽고 순서를 바꿉니다.
  5. 상위 문서만 LLM에 전달합니다.

처음에는 30개에서 100개 정도를 찾은 뒤 5개에서 10개로 줄이는 구성을 시험할 수 있습니다. 후보가 많을수록 재정렬 비용과 응답 시간이 늘어나므로 실제 질문으로 조정해야 합니다.

검색 필터는 보안과 품질에 모두 필요합니다

문서에는 부서, 사용자, 문서 종류, 언어, 공개 범위 같은 정보가 있습니다. 이 값을 검색 조건으로 사용해야 합니다.

필터가 없으면 권한이 없는 문서까지 검색될 수 있습니다. 반대로 필터가 너무 강하거나 문서 정보가 빠져 있으면 필요한 문서를 놓칩니다. 검색에 사용한 필터와 제외된 문서 수를 로그에 남기면 원인을 찾기 쉽습니다.

Qdrant는 문서 속성을 빠르게 거르는 payload index를 데이터 적재 전에 만들도록 권장합니다. pgvector는 필터 때문에 검색 결과가 부족할 때 검색 범위를 단계적으로 넓히는 iterative scan 옵션을 제공합니다.

HNSW와 IVFFlat의 차이

벡터가 많아지면 모든 벡터를 하나씩 비교하기 어렵습니다. HNSW와 IVFFlat은 가까울 가능성이 높은 벡터만 빠르게 확인하는 근사 검색 인덱스입니다.

근사 검색을 적용하기 전에는 데이터가 적을 때 정확 검색으로 기준을 만듭니다. 정확 검색 결과와 비교해야 HNSW나 IVFFlat이 관련 문서를 얼마나 놓치는지 알 수 있습니다.

HNSW

HNSW는 벡터 사이의 연결 그래프를 만듭니다. 검색 속도가 빠르고 관련 문서를 놓치는 비율도 낮은 편입니다. 데이터가 계속 추가되는 서비스에서 많이 사용하지만 인덱스를 만드는 시간이 길고 메모리를 더 사용합니다.

  • m: 각 벡터가 연결되는 수
  • ef_construction: 인덱스를 만들 때 확인할 후보 수
  • ef_search: 검색할 때 확인할 후보 수

ef_search를 높이면 관련 문서를 찾을 가능성이 커지지만 검색 시간도 늘어납니다. Qdrant의 hnsw_ef도 같은 방식으로 조정합니다.

IVFFlat

IVFFlat은 벡터를 여러 묶음으로 나눈 뒤 질문과 가까운 묶음부터 검색합니다. HNSW보다 인덱스를 빠르게 만들 수 있고 메모리 부담도 낮을 수 있습니다. 다만 데이터 분포를 먼저 학습해야 하므로 데이터가 쌓인 뒤 만드는 편이 좋습니다.

  • lists: 벡터를 나눌 묶음 수
  • probes: 검색할 때 확인할 묶음 수

probes를 높이면 더 많은 묶음을 확인하므로 재현율과 검색 시간이 함께 늘어납니다.

인덱스를 비교할 때는 벡터 수, 검색량, 삽입량, 메모리, 필터 비율, 허용 응답 시간을 함께 확인합니다. 데이터가 자주 추가된다면 HNSW가 편할 수 있습니다. 메모리가 부족하고 데이터를 한 번에 적재한다면 IVFFlat도 좋은 선택입니다. 최종 선택은 같은 평가 질문으로 비교하여 정합니다.

검색과 답변을 따로 평가합니다

답변이 틀렸을 때 LLM만 바꾸면 원인을 찾기 어렵습니다. 먼저 관련 문서가 검색됐는지 확인해야 합니다.

검색 단계에서는 아래 값을 봅니다.

  • Recall@k: 관련 문서가 상위 k개 안에 들어왔는지
  • Precision@k: 검색된 문서 중 실제 관련 문서의 비율
  • MRR: 첫 번째 관련 문서가 얼마나 앞에 있는지
  • 검색과 재정렬에 걸린 시간
  • 필터 때문에 결과가 0개가 된 비율

답변 단계에서는 아래 항목을 확인합니다.

  • 답변이 검색된 문서에 근거했는지
  • 질문에 필요한 내용을 직접 답했는지
  • 표시한 출처가 실제 근거와 일치하는지
  • 근거가 없을 때 답변을 거절하는지
  • 토큰 사용량과 비용

Ragas는 검색 문맥의 정확성(Context Precision), 누락 여부(Context Recall), 답변 관련성(Response Relevancy), 근거 충실도(Faithfulness)를 평가할 수 있습니다. 자동 평가는 반복 테스트에 유용하지만 중요한 질문과 실패 사례는 사람이 함께 확인해야 합니다.

운영 로그에는 원본 질문, 적용한 권한 필터, 검색 후보 ID, 재정렬 순위, LLM에 전달한 청크 ID, 문서 버전, 단계별 처리 시간을 남깁니다. 원문 전체나 개인정보를 무조건 저장하지는 않습니다. 문제를 추적하는 데 필요한 ID와 마스킹된 정보만 보존합니다.

처음 구현할 때는 이 순서로 진행합니다

  1. 실제 질문과 관련 문서를 묶어 평가용 질문을 만듭니다.
  2. 문서의 제목과 문단을 살려 청킹합니다.
  3. 데이터가 적을 때 정확 벡터 검색으로 첫 결과를 확인합니다.
  4. 임베딩 모델 두세 개를 같은 질문으로 비교합니다.
  5. 코드나 고유명사가 많다면 BM25를 추가합니다.
  6. 관련 문서는 찾았지만 순위가 낮을 때 재정렬을 추가합니다.
  7. 검색 시간이 길어지면 HNSW와 IVFFlat을 비교합니다.
  8. 권한 필터가 있는 상태에서 검색 결과가 줄어드는지 확인합니다.
  9. 운영에서 실패한 질문을 평가 목록에 계속 추가합니다.

새 기능은 문제가 생긴 단계에 추가해야 합니다. 관련 문서가 청크로 만들어지지 않았다면 재정렬 모델을 바꿔도 해결되지 않습니다. 관련 문서는 검색됐지만 순위가 낮다면 청킹 전체를 바꾸기보다 재정렬부터 시험할 수 있습니다.

자주 묻는 질문

청크는 몇 토큰이 좋은가요?

문서마다 다릅니다. 일반 문서는 400에서 800토큰을 먼저 비교할 수 있습니다. FAQ는 더 짧게 나누고 정책 문서는 조항 단위를 우선합니다. 최소 두세 가지 크기를 같은 질문으로 비교하세요.

처음부터 Semantic Chunking을 써야 하나요?

필수는 아닙니다. 먼저 제목과 문단을 살린 청킹으로 시작합니다. 관련 정보가 청크 경계에서 계속 잘릴 때 Semantic Chunking과 비교해도 늦지 않습니다.

PostgreSQL과 pgvector로 시작해도 되나요?

이미 PostgreSQL을 운영하고 있고 벡터 수와 검색량을 감당할 수 있다면 pgvector로 시작해도 됩니다. 벡터 규모가 매우 크거나 복잡한 혼합 검색이 필요해질 때 전용 검색 시스템을 비교할 수 있습니다.

임베딩 모델만 바꾸고 기존 벡터를 유지할 수 있나요?

대부분 어렵습니다. 모델마다 벡터 공간이 다르기 때문에 문서와 질문을 새 모델로 다시 임베딩해야 합니다.

답변이 틀리면 LLM부터 바꿔야 하나요?

관련 문서가 검색됐는지 먼저 확인합니다. 관련 문서가 없다면 청킹이나 검색 문제입니다. 관련 문서가 있는데 답변이 틀렸다면 LLM에 전달한 문맥과 프롬프트를 확인합니다.

정리

RAG는 문서를 검색하고 그 근거로 답변하는 백엔드 시스템입니다. 청킹은 문서의 의미가 끊기지 않도록 나눕니다. 임베딩 모델은 실제 질문으로 비교합니다. 벡터 검색과 BM25를 함께 사용하면 의미가 비슷한 문장과 정확한 코드 검색을 모두 처리하기 좋습니다.

처음부터 복잡한 기능을 모두 넣을 필요는 없습니다. 평가용 질문을 먼저 만들고 청킹, 검색, 재정렬, 인덱스를 순서대로 추가하면 어떤 변경이 도움이 됐는지 확인하기 쉽습니다.

태그: RAG 아키텍처, 백엔드 개발자, Chunking 전략, 임베딩 모델 비교, Vector DB, HNSW, IVFFlat, pgvector, 하이브리드 검색, Reranking, LLM, 생성형 AI, AI Agent

 

댓글
Total
최근에 올라온 글