Skip to content

Split View: 프로덕션 RAG 패턴 — 순진한 RAG가 실패하는 이유와 실제로 통하는 기법들

|

프로덕션 RAG 패턴 — 순진한 RAG가 실패하는 이유와 실제로 통하는 기법들

들어가며 — 순진한 RAG는 왜 실패하나

RAG(검색 증강 생성)의 기본 아이디어는 단순합니다 — 질문과 관련 있는 문서를 찾아 프롬프트에 붙이고, 모델이 그걸 근거로 답하게 한다. 2020년 Lewis 등의 원논문은 이를 파라메트릭 기억(모델 가중치)과 논파라메트릭 기억(외부 인덱스)의 결합으로 정식화했습니다. 데모는 반나절이면 만듭니다 — 문서를 자르고, 임베딩하고, 벡터 DB에 넣고, 질문과 가장 가까운 상위 k개를 붙이면 끝입니다.

문제는 그 순진한 파이프라인이 프로덕션에서 조용히 무너진다는 데 있습니다. 그리고 실패는 대개 생성이 아니라 검색 에서 옵니다.

  • 벡터 검색은 의미는 잘 잡지만 정확한 토큰 일치를 놓칩니다 — 에러 코드, 제품 SKU, 사람 이름, API 이름 같은 것들.
  • 문서에서 잘라낸 조각은 자기 맥락을 잃습니다. "매출이 3% 늘었다"는 청크만으로는 어느 회사, 어느 분기인지 알 수 없습니다.
  • 검색기는 틀려도 그럴듯한 조각을 조용히 반환하고, 모델은 그걸 자신 있게 인용합니다. 환각처럼 보이지만 실은 검색 실패입니다.

핵심 직관은 하나입니다 — 검색 품질이 하류의 모든 것을 상한선으로 묶습니다. 관련 문서가 애초에 컨텍스트에 안 들어오면, 어떤 프롬프트 엔지니어링도 그걸 되살리지 못합니다. 그래서 아래 기법들은 대부분 "생성을 더 똑똑하게"가 아니라 "올바른 조각이 컨텍스트에 들어오게"를 겨냥합니다.

컨텍스트 창을 키워 "전부 다 넣기"로 우회하려는 유혹도 있지만, 그건 해법이 아닙니다 — 긴 컨텍스트에서 모델은 중간에 묻힌 정보를 놓치기 쉽고(이른바 lost-in-the-middle), 토큰 비용과 지연도 함께 커지며, 무엇보다 애초에 올바른 문서가 그 안에 있어야 한다는 문제는 그대로입니다.

청킹 — 조용히 품질을 결정하는 선택

무엇인가. 문서를 검색·임베딩 단위로 쪼개는 일입니다. 흔한 실수는 고정 길이(예: 문자 수)로 기계적으로 자르는 것 — 문장 중간, 표 한복판, 코드 블록 한가운데를 갈라 놓습니다.

언제 도움이 되나. 경계를 의미 단위로 두면 검색 정확도가 오릅니다. 실무의 기본기는 두 가지입니다. (1) 구조를 존중해 자르기 — 문단·헤딩·표·코드 단위. (2) 슬라이딩 윈도우/오버랩 — 인접 청크가 조금 겹치게 해서 경계에 걸친 문장이 통째로 사라지지 않게 합니다. "Searching for Best Practices in RAG"(2024)는 대체로 256–512 토큰 대의 작은 청크가 충실도(faithfulness) 면에서 유리했다고 보고합니다.

대가. 청크가 작을수록 정밀하지만 맥락이 얇아지고, 클수록 맥락은 풍부하지만 한 청크에 잡음이 섞여 검색 신호가 흐려집니다. 정답은 코퍼스마다 다릅니다 — 그래서 청크 크기는 추측이 아니라 평가로 정할 값입니다.

임베딩과 하이브리드 검색 (BM25 + 벡터)

무엇인가. 임베딩 검색(밀집·dense)은 텍스트를 벡터로 바꿔 의미적 유사도 로 찾습니다. BM25(희소·sparse)는 TF-IDF 계열의 어휘 매칭으로 정확한 단어 일치 를 찾습니다. 둘은 실패 방식이 다릅니다 — 벡터는 동의어·의역에 강하지만 희귀 토큰을 놓치고, BM25는 정확한 토큰에 강하지만 표현이 달라지면 못 찾습니다.

언제 도움이 되나. 거의 항상. 대부분의 프로덕션 코퍼스에는 두 종류의 질의가 섞여 있습니다 — "환불 정책 요약해줘"(의미) 와 "ERR_2043 이 뭐야"(어휘). 그래서 하이브리드(둘을 함께 돌리고 결과를 병합)가 강력한 기본값입니다. 병합은 흔히 RRF(Reciprocal Rank Fusion) 로, 두 랭킹의 순위를 역수로 합산해 한 쪽 점수 스케일에 휘둘리지 않게 합니다.

대가. 인덱스를 두 벌(벡터 + 역색인) 운영해야 하고, 융합 가중치라는 튜닝 손잡이가 하나 늘어납니다. 그래도 대개 가장 ROI가 높은 첫걸음입니다 — 순수 밀집 검색이 구조적으로 놓치는 정확 일치 질의를 되살려 주기 때문입니다.

메타데이터 필터링 — 검색 공간을 먼저 좁히기

무엇인가. 벡터·BM25 점수를 매기기 전에(또는 그와 함께) 문서의 속성으로 후보를 걸러 내는 것입니다 — 테넌트 ID, 작성일, 문서 유형, 그리고 무엇보다 접근 권한(ACL).

언제 도움이 되나. 멀티테넌트 SaaS나 권한이 갈리는 사내 지식베이스에서는 선택이 아니라 필수입니다. 사용자가 볼 수 없는 문서를 검색 결과로 흘리면 그건 품질 문제가 아니라 보안 사고 입니다. 부수 효과로 검색 공간이 줄어 정밀도와 지연도 함께 좋아집니다.

대가. 깨끗한 메타데이터를 색인 시점에 붙여 둬야 하고, 너무 좁게 걸면 정답 문서까지 잘라 낼 수 있습니다. 필터는 랭킹이 아니라 하드 제약이라는 점을 기억하세요.

리랭킹 — 상위 결과의 정밀도

무엇인가. 1차 검색이 넉넉히(예: 상위 20–100개) 후보를 건져 오면, 크로스 인코더 리랭커가 (질의, 문서) 쌍을 함께 읽어 관련도를 다시 매깁니다. 임베딩은 질의와 문서를 따로 벡터화(바이 인코더)하지만, 리랭커는 둘을 한 번에 보므로 훨씬 정밀합니다.

언제 도움이 되나. 1차 검색의 재현율(recall)은 괜찮은데 상위권 정밀도가 아쉬울 때. 경험칙은 "넓게 건지고 좁게 추려라" — 20개를 검색해 5개로 리랭킹하고, 그중 3–5개를 모델에 넘깁니다. 앞의 베스트 프랙티스 논문은 리랭킹 모듈을 빼면 성능이 눈에 띄게 떨어졌다며 그 필요성을 강조합니다.

대가. 크로스 인코더는 후보마다 순전파를 한 번씩 돌리므로 지연·비용이 붙습니다. 그래서 전체가 아니라 짧은 후보 목록 에만 적용합니다. 후보를 100개, 200개로 무한정 늘려 리랭킹하는 건 대개 본전을 못 뽑습니다.

컨텍스추얼 리트리벌 — 고립된 청크 문제 고치기

무엇인가. 청킹이 만드는 근본 결함 하나 — 잘라낸 조각은 원문의 맥락을 잃습니다. Anthropic의 컨텍스추얼 리트리벌 은 임베딩·BM25 색인 전에, 각 청크 앞에 그 청크가 문서 전체에서 어디에 속하는지 설명하는 짧은 맥락(보통 50–100 토큰)을 LLM으로 생성해 덧붙입니다. "이 청크는 2023년 3분기 ACME의 실적 보고서에서 발췌"처럼요.

언제 도움이 되나. 대명사·상대 참조가 많고 문서가 긴 코퍼스에서 특히. Anthropic이 보고한 수치는 인상적입니다 — recall@20 기준 실패율(1 − recall@20)이 컨텍스추얼 임베딩만으로 35% 감소(5.7% → 3.7%), 컨텍스추얼 BM25까지 더하면 49% 감소(→ 2.9%), 여기에 리랭킹까지 얹으면 67% 감소(→ 1.9%)였습니다.

대가. 청크마다 LLM을 한 번씩 호출하는 색인 시간 비용이 듭니다. Anthropic은 프롬프트 캐싱으로 문서 100만 토큰당 약 $1.02 수준이라고 밝혔지만, 문서가 갱신될 때마다 다시 돌려야 하고 파이프라인 복잡도도 올라갑니다. 정적이고 참조가 얽힌 지식베이스일수록 값어치를 합니다.

쿼리 재작성 — 검색기를 중간에서 만나 주기

무엇인가. 사용자의 원 질문을 검색 전에 다듬는 단계입니다. 여러 형태가 있습니다 — 대화형에서 후속 질문의 대명사를 풀어 독립 질의로 만들기(디컨텍스추얼라이즈), 약어·전문용어 확장, 복합 질문을 하위 질의로 분해(멀티 쿼리), 또는 HyDE 처럼 가상의 답을 먼저 생성해 그걸 임베딩해 검색하기.

언제 도움이 되나. 멀티턴 대화, 짧고 모호한 질의, 사용자 어휘와 문서 어휘가 어긋나는 경우. 질문이 이미 잘 정리돼 있으면 오히려 덜 필요합니다.

대가. LLM 호출이 하나 더 붙어 지연·비용이 늘고, 새 실패 모드 가 생깁니다 — 재작성이 빗나가면 원 질의보다 못한 결과를 부릅니다. 그래서 재작성 역시 유무를 평가로 비교해야 할 대상이지, 무조건 켜 두는 스위치가 아닙니다.

평가 — 검색 지표와 종단(end-to-end)을 나눠서, 자기 데이터로

두 층을 분리하라. RAG 평가는 반드시 두 갈래로 봐야 합니다.

  • 검색 지표 — recall@k, precision@k, MRR, nDCG. "올바른 청크가 컨텍스트에 들어왔는가"를 잽니다. 질의→관련 문서 라벨 셋이 필요합니다.
  • 종단(생성) 지표 — 충실도/근거성(답이 검색된 맥락에 실제로 뒷받침되는가 = 환각 여부), 답변 관련성, 답변 정확성. 흔히 LLM-as-judge로 채점합니다.

왜 나눠야 하나 — 똑똑한 생성기가 나쁜 검색을 가려 버리기 때문입니다(그 반대도). 종단 점수만 보면 검색이 새는데도 괜찮아 보일 수 있습니다. 검색 recall이 낮으면 프롬프트로는 못 살립니다 — 층을 나눠야 병목이 어디인지 보입니다.

프로덕션에서는 실패를 두 통에 나눠 담는 습관이 유용합니다 — (a) 올바른 문서가 애초에 검색되지 않았다, (b) 검색은 됐는데 생성이 무시하거나 잘못 썼다. (a)는 검색 단(청킹·하이브리드·리랭킹)을 고치고, (b)는 프롬프트·인용 강제·모델을 손봅니다. 이 분류 없이 종단 점수만 쳐다보면 엉뚱한 곳을 계속 고치게 됩니다.

공개 벤치마크는 당신의 코퍼스를 예측하지 못합니다. 실제 질의에서 뽑은 작은 라벨 셋(수십 개면 시작으로 충분)을 만들어 각 기법의 유무를 A/B로 비교하세요. 이건 모델보다 평가 셋이 먼저라는 eval-first 원칙의 RAG 판입니다. 평가가 먼저 있으면 청크 크기·융합 가중치·리랭커 선택이 취향이 아니라 실험이 됩니다.

실용적인 도입 순서

한 번에 다 넣지 마세요. 대개 이 순서가 ROI가 높습니다.

  1. 구조를 존중하는 청킹 + 오버랩부터. 여기서 새면 뒷단이 다 샙니다.
  2. 하이브리드 검색(BM25 + 벡터, RRF 융합) — 정확 일치 질의를 되살리는 가장 값싼 큰 개선.
  3. 리랭킹 — 넓게 건져 좁게 추리기. 상위권 정밀도를 끌어올립니다.
  4. 참조가 얽힌 긴 문서라면 컨텍스추얼 리트리벌.
  5. 대화형·모호 질의가 많으면 쿼리 재작성.

멀티테넌트이거나 권한이 갈리는 코퍼스라면, 메타데이터 필터링(특히 접근 권한)은 이 순서와 별개로 처음부터 기본값이어야 합니다 — 나중에 얹는 최적화가 아니라 1일차 요건입니다.

그리고 이 모든 단계 이전에 작은 평가 셋을 먼저 만드세요. 그래야 각 단계가 실제로 도움이 됐는지 숫자로 답할 수 있습니다.

마치며

RAG는 마법이 아닙니다 — LLM에 검색을 붙였다고 지식 문제가 사라지지 않습니다. 그저 검색 문제로 바뀔 뿐입니다. 그리고 검색은 수십 년 묵은, 여전히 어려운 분야입니다. 다행히 좋은 소식은, 여기서 가장 큰 개선이 대개 가장 지루한 곳에서 나온다는 점입니다 — 더 큰 모델이 아니라, 더 나은 청킹과 하이브리드 검색, 그리고 넉넉히 건져 리랭킹하는 습관에서.

한 가지만 가져간다면 이것입니다 — 자기 데이터로 평가하라. 위 기법들은 도구 상자이지 레시피가 아닙니다. 어떤 코퍼스에서는 컨텍스추얼 리트리벌이 판을 바꾸고, 다른 곳에서는 BM25 하나 켜는 게 전부일 수 있습니다. 그 차이는 벤치마크가 아니라 당신의 질의에서만 드러납니다.

참고 자료

Production RAG Patterns — Why Naive RAG Fails and the Techniques That Actually Help

Introduction — Why naive RAG fails

The core idea of RAG (retrieval-augmented generation) is simple: find documents relevant to a question, paste them into the prompt, and let the model answer grounded in them. The 2020 paper by Lewis et al. formalized this as coupling parametric memory (model weights) with non-parametric memory (an external index). A demo takes half a day: split the documents, embed them, drop them into a vector database, and attach the top k nearest to the question.

The trouble is that this naive pipeline quietly falls apart in production — and the failure is almost always in retrieval, not generation.

  • Vector search captures meaning well but misses exact token matches — error codes, product SKUs, people's names, API names.
  • A fragment cut out of a document loses its own context. A chunk that says "revenue grew 3%" no longer knows which company or which quarter.
  • The retriever quietly returns a plausible-but-wrong chunk when it is wrong, and the model cites it confidently. It looks like a hallucination, but it is a retrieval failure.

The key intuition is one line: retrieval quality caps everything downstream. If the relevant document never enters the context, no amount of prompt engineering brings it back. So most of the techniques below aim not at "making generation smarter" but at "getting the right chunk into the context."

There is also a temptation to sidestep all this by enlarging the context window and "stuffing everything in." It is not a fix — models tend to miss information buried in the middle of a long context (the so-called lost-in-the-middle effect), token cost and latency grow, and above all the underlying problem remains: the right document still has to be in there in the first place.

Chunking — the decision that quietly caps quality

What it is. Splitting a document into the units you retrieve and embed. The common mistake is fixed-length slicing (say, by character count), which cuts mid-sentence, through the middle of a table, or across a code block.

When it helps. Aligning boundaries with meaning raises retrieval accuracy. Two basics carry most of the weight: (1) respect structure — split on paragraphs, headings, tables, code; (2) use a sliding window / overlap so adjacent chunks share a little text and a sentence straddling a boundary is not lost entirely. "Searching for Best Practices in RAG" (2024) reports that smaller chunks in the 256–512 token range tended to do better on faithfulness.

The tradeoff. Smaller chunks are more precise but thinner on context; larger ones carry richer context but mix in noise that dilutes the retrieval signal. The right size is corpus-specific — which is exactly why chunk size is a value to be measured, not guessed.

Embeddings and hybrid search (BM25 + vector)

What it is. Embedding search (dense) turns text into vectors and matches on semantic similarity. BM25 (sparse) is TF-IDF-family lexical matching that finds exact word overlap. They fail in different ways: vectors are strong on synonyms and paraphrase but miss rare tokens; BM25 is strong on exact tokens but breaks when the wording differs.

When it helps. Almost always. Most production corpora carry both kinds of query — "summarize the refund policy" (semantic) and "what is ERR_2043" (lexical). That makes hybrid search (run both and merge the results) a powerful default. Merging is often done with Reciprocal Rank Fusion (RRF), which combines the two rankings by reciprocal rank so neither score scale dominates.

The tradeoff. You now run two indexes (a vector store and an inverted index) and gain one more tuning knob in the fusion weights. Even so, it is usually the highest-ROI first move, because it recovers the exact-match queries that pure dense retrieval structurally misses.

Metadata filtering — narrow the search space first

What it is. Filtering candidates by document attributes before (or alongside) scoring them with vector or BM25 — tenant ID, creation date, document type, and above all access permissions (ACLs).

When it helps. In multi-tenant SaaS or a permissioned internal knowledge base, it is not optional but mandatory. Leaking a document the user cannot see into the results is not a quality issue — it is a security incident. As a side effect, narrowing the search space also improves precision and latency.

The tradeoff. You must attach clean metadata at index time, and filtering too tightly can cut out the very document you needed. Remember that a filter is a hard constraint, not a ranking signal.

Reranking — precision at the top

What it is. The first-stage retriever pulls a generous candidate set (say, the top 20–100), then a cross-encoder reranker reads each (query, document) pair together and rescores relevance. Embeddings vectorize query and document separately (a bi-encoder); a reranker looks at both at once, so it is far more precise.

When it helps. When first-stage recall is fine but top-of-list precision disappoints. The rule of thumb is "retrieve wide, keep narrow" — retrieve 20, rerank to 5, hand 3–5 to the model. The best-practices paper above emphasizes reranking's necessity, noting that removing the module caused a noticeable drop in performance.

The tradeoff. A cross-encoder runs one forward pass per candidate, so it adds latency and cost. That is why you apply it only to a short candidate list. Reranking an ever-growing pool of 100 or 200 candidates rarely pays for itself.

Contextual retrieval — fixing the isolated-chunk problem

What it is. Chunking creates one fundamental defect: a fragment loses the surrounding context of its source. Anthropic's contextual retrieval prepends, before embedding and BM25 indexing, a short LLM-generated explanation (typically 50–100 tokens) that situates each chunk within the whole document — something like "This chunk is from ACME's Q3 2023 earnings report."

When it helps. Especially on long corpora full of pronouns and relative references. The numbers Anthropic reported are striking: measured as failure rate at recall@20 (1 − recall@20), contextual embeddings alone cut failures by 35% (5.7% → 3.7%), adding contextual BM25 cut them by 49% (→ 2.9%), and adding reranking on top reached a 67% reduction (→ 1.9%).

The tradeoff. You pay an index-time cost of one LLM call per chunk. Anthropic put it at roughly $1.02 per million document tokens with prompt caching, but you must re-run it whenever documents change, and pipeline complexity goes up. It earns its keep most on static, reference-tangled knowledge bases.

Query rewriting — meeting the retriever halfway

What it is. A step that reshapes the user's original question before retrieval. It takes several forms: decontextualizing a follow-up in a conversation into a standalone query, expanding acronyms and jargon, decomposing a compound question into sub-queries (multi-query), or HyDE, where you first generate a hypothetical answer and embed that to search.

When it helps. Multi-turn conversations, short or vague queries, and cases where the user's vocabulary and the document's vocabulary diverge. When the question is already well-formed, it helps less.

The tradeoff. It adds another LLM call — more latency and cost — and introduces a new failure mode: a bad rewrite retrieves worse than the original query would have. So rewriting, too, is something to compare with and without via evaluation, not a switch you leave permanently on.

Evaluation — retrieval metrics vs end-to-end, on your own data

Separate the two layers. RAG evaluation must be seen in two strands.

  • Retrieval metrics — recall@k, precision@k, MRR, nDCG. They measure "did the right chunk make it into the context?" and need a labeled query-to-relevant-document set.
  • End-to-end (generation) metrics — faithfulness/groundedness (is the answer actually supported by the retrieved context, i.e. no hallucination), answer relevance, answer correctness. Often scored with an LLM-as-judge.

Why separate them: a strong generator can mask weak retrieval (and vice versa). Look only at the end-to-end score and things can seem fine while retrieval is leaking. If retrieval recall is low, no prompt saves you — splitting the layers is how you see where the bottleneck is.

In production it helps to sort failures into two bins: (a) the right document was never retrieved, and (b) it was retrieved but generation ignored or misused it. Bucket (a) is fixed in the retrieval stage — chunking, hybrid, reranking — while (b) is fixed in the prompt, citation enforcement, or the model. Without this split, staring at the end-to-end score alone keeps you fixing the wrong thing.

Public benchmarks do not predict your corpus. Build a small labeled set from real queries (a few dozen is enough to start) and A/B each technique with and without. This is the RAG version of the eval-first principle: the eval set comes before the model. Once the eval exists, chunk size, fusion weights, and reranker choice stop being taste and become experiments.

A pragmatic build order

Don't add everything at once. This order usually pays off best.

  1. Structure-respecting chunking with overlap first. If it leaks here, everything downstream leaks.
  2. Hybrid search (BM25 + vector, fused with RRF) — the cheapest large win, because it recovers exact-match queries.
  3. Reranking — retrieve wide, keep narrow. It lifts top-of-list precision.
  4. Contextual retrieval if your documents are long and reference-tangled.
  5. Query rewriting if you have many conversational or vague queries.

If your corpus is multi-tenant or permissioned, metadata filtering (access control above all) sits outside this order and should be a default from the start — a day-one requirement, not a later optimization.

And before any of these, build the small eval set — so you can answer with numbers whether each step actually helped.

Closing

RAG is not magic — bolting retrieval onto an LLM does not make the knowledge problem disappear. It just turns it into a retrieval problem, and retrieval is a decades-old, still-hard field. The good news is that the biggest gains here usually come from the most boring places: not a bigger model, but better chunking, hybrid search, and the habit of retrieving generously and then reranking.

If you take one thing away, take this: evaluate on your own data. The techniques above are a toolbox, not a recipe. On one corpus contextual retrieval changes the game; on another, simply turning on BM25 is the whole story. That difference shows up not in a benchmark but only in your queries.

References