RAG는 쿼리 시점에 문서를 검색해 프롬프트에 추가함으로써 LLM이 기억이 아닌 데이터를 근거로 답하게 하는 기법입니다. Lewis 등이 2020년에 제안한 이 기법은, 일반 모델이 책을 덮고 치르는 시험이라면 책을 펴 놓고 치르는 오픈북 시험에 해당합니다. 모델은 학습 중에 외운 내용에 기대는 대신 사용자가 제공한 자료를 참고해 답합니다. 그래서 답변은 근거가 있고, 최신이며, 비공개 데이터를 반영할 수 있고, 출처가 알려져 있으므로 인용할 수 있습니다. 과장된 홍보 뒤에 있는, 백엔드 개발자에게 반가운 사실은 이것입니다. RAG는 반드시 도입해야 하는 프레임워크가 아니라 이미 직접 만들 수 있는 for 루프입니다.
핵심 요약
| 질문 | 답변 |
|---|---|
| 핵심 동작 | 관련 청크 검색 → 프롬프트에 추가 → 근거 있는 답변 생성 |
| 해결하는 세 가지 공백 | 환각 · 학습 데이터 기준일 · 비공개 데이터 접근 불가 |
| vs. 파인튜닝 | RAG는 지식을 제공, 파인튜닝은 동작을 변경 – 둘 다 쓰는 경우가 많음 |
| 가장 흔한 오류 | 검색 실패 – 엉뚱한 청크 입력, 자신만만한 오답 출력 |
| 프로덕션 필수 사항 | 유사도만이 아니라 권한으로 검색 필터링 |
두 단계
수집 (오프라인, 문서당 한 번)
로드 → 청크 분할 (수백 토큰 내외로 분할, 겹침 포함)
→ 각 청크를 벡터로 임베딩
→ 벡터 + 텍스트 + ACL 메타데이터를 인덱스에 저장
쿼리 (온라인, 질문마다)
질문 임베딩 → 유사도 검색으로 top-k 청크 조회 (보통 5–10개)
→ 바로 이 사용자가 읽을 수 있는 것만 남기도록 필터링
→ 청크로 프롬프트 보강
→ LLM이 청크에 근거한 답변 생성 (+ 출처 인용)
온라인 단계 전체를 서버 측에서 함수 하나로 처리하면 다음과 같습니다:
// JavaScript — Cloud Code (cloud/main.js): RAG is a for-loop, not a framework
Parse.Cloud.define('askDocs', async (req) => {
// 1 · embed the question (LLM API key stays server-side)
const qVec = await embed(req.params.question);
// 2 · retrieve — similarity search, ACL-FILTERED to this user's docs
const chunks = await vectorSearch('DocChunk', qVec, {
limit: 6,
aclUser: req.user, // never retrieve what the asker can't read
});
// 3 · augment + generate
const context = chunks.map((c) => c.get('text')).join('\n---\n');
return await complete(`Answer using ONLY this context:\n${context}\n\nQ: ${req.params.question}`);
// Answer cites the retrieved chunks — grounded, and permission-safe.
}); // Flutter / Dart — Back4app Flutter SDK
// The client just asks — retrieval, grounding, and ACLs happen server-side
final answer = await ParseCloudFunction('askDocs')
.execute(parameters: {'question': 'What is our refund window?'});
print(answer.result); // grounded in the user's own documents, with citations
// No embeddings, no vector math, no LLM key on the device — RAG's backend
// is ordinary backend work: store docs, index vectors, query, call the model. // iOS / Swift — Back4app Swift SDK
// The client just asks — retrieval, grounding, and ACLs happen server-side
let answer: String = try await Cloud.run(
name: "askDocs",
parameters: ["question": "What is our refund window?"])
print(answer) // grounded in the user's own documents, with citations
// No embeddings, no vector math, no LLM key on the device — RAG's backend
// is ordinary backend work: store docs, index vectors, query, call the model. // Android / Kotlin — Back4app Android SDK
// The client just asks — retrieval, grounding, and ACLs happen server-side
val params = mapOf("question" to "What is our refund window?")
val answer = ParseCloud.callFunction<String>("askDocs", params)
println(answer) // grounded in the user's own documents, with citations
// No embeddings, no vector math, no LLM key on the device — RAG's backend
// is ordinary backend work: store docs, index vectors, query, call the model. RAG가 필요한 이유: 세 가지 공백
일반 LLM에는 세 가지 구조적 약점이 있으며, RAG는 재학습 없이 이 모두를 해결합니다. 환각: 모델은 알든 모르든 자신 있게 답합니다. 모든 주장을 검색된 텍스트에 그라운딩하면 모델에게 말할 수 있는 사실을 쥐여 주게 됩니다. 학습 데이터 기준일(training cutoff): 모델의 지식은 학습 시점에 멈춥니다. 검색은 오늘의 데이터를 가져옵니다. 비공개 데이터: 모델은 사용자의 문서를 본 적이 없습니다. 검색은 모델이 쿼리 시점에 그 문서를 읽는 방법입니다. 책을 덮은 모델은 줄 수 없는 보너스도 있습니다. 검색된 청크가 알려져 있으므로 답변은 각주처럼 출처를 인용할 수 있으며, 이는 RAG가 지닌 가장 큰 신뢰상의 이점입니다.
RAG vs. 파인튜닝
| RAG | 파인튜닝 | |
|---|---|---|
| 바꾸는 것 | 지식 – 모델이 아는 것 | 동작 – 모델이 응답하는 방식 |
| 업데이트 속도 | 즉시 – 문서 추가 | 바꾸려면 재학습 |
| 최신성 | 항상 최신 | 학습 시점에 고정 |
| 출처 인용 | 가능 – 출처가 알려져 있음 | 불가 |
| 비용 구조 | 쿼리당 검색 + 토큰 | 선행 학습 비용 |
| 적합한 용도 | 사실, 비공개 문서, 자주 바뀌는 정보 | 문체, 형식, 도메인 추론 |
둘은 경쟁 관계가 아닙니다. 파인튜닝은 모델에게 어떻게 답할지를 가르치고, RAG는 무엇에 대해 답할지를 제공합니다. 특화된 도메인 어시스턴트는 둘 다 쓰는 경우가 많습니다. 파인튜닝된 말투로, 검색된 지식 베이스를 근거로 답하는 식입니다.
RAG는 끝났을까? 긴 컨텍스트 문제에 대한 답
주요 키워드를 다루는 페이지들이 피해 가는 솔직한 설명입니다. 이제 컨텍스트 윈도우는 수백만 토큰에 이르렀고, “그냥 전부 프롬프트에 넣으면 된다”는 주장이 되풀이해서 등장합니다. 그래도 검색이 살아남는 이유는 세 가지 사실에 있습니다. 비용과 지연 시간: 거대한 컨텍스트를 모든 쿼리마다 처리하는 비용은 관련된 부분만 가져오는 것보다 훨씬 비싸고 느립니다. 규모가 커지면 몇 자릿수 차이가 납니다. 컨텍스트 부패(context rot): 2025년까지의 연구들은 윈도우가 차기 훨씬 전부터 모델 정확도가 떨어진다는 사실을 발견했습니다. 거대한 컨텍스트에 묻힌 관련 사실은 놓치기 쉬우므로, 윈도우가 크다고 해서 답변이 확실히 좋아지는 것은 아닙니다. 최신성과 접근 제어: 정적인 거대 프롬프트는 데이터가 바뀌는 순간 낡은 정보가 되고, 누가 무엇을 읽을 수 있는지도 알지 못합니다. 2026년의 합의는 “RAG냐 긴 컨텍스트냐”가 아니라 둘 다입니다. 넉넉하고 관련성 있으며 권한으로 필터링된 부분 집합을 검색한 다음, 긴 컨텍스트 모델로 그 위에서 추론합니다. 순수한 긴 컨텍스트 방식은 작고 안정적이며 민감하지 않은 코퍼스에만 적합합니다.
RAG가 실패하는 지점: 검색
RAG 시스템을 디버깅하는 방식을 바꿔 놓는 관점이 있습니다. 잘못 검색된 쓰레기는 자신만만하게 생성된 쓰레기가 됩니다. 모델 탓으로 돌리는 실패의 대부분은 생성의 탈을 쓴 검색 실패입니다. 엉뚱한 청크를 가져왔으니, 완벽하게 충실한 답변도 엉뚱한 자료에 근거하게 됩니다. 그래서 실제로 품질을 좌우하는 변수는 두 가지입니다. 청킹: 조각이 너무 크면 관련성이 희석되고 프롬프트 예산을 소모하며, 너무 작으면 그 조각을 의미 있게 만들던 맥락을 잃습니다. 제목, 문단, 코드 블록 단위로 나누는 구조 인식 분할이 대개 고정 크기보다 낫습니다(전략이 중요합니다). 하이브리드 검색: 키워드 검색(정확한 용어, 이름, ID)과 벡터 검색(의미)을 결합한 다음, 프롬프트에 넣기 전에 더 강력한 관련성 모델로 합쳐진 후보를 리랭킹합니다. 재현율은 두 검색에서, 정밀도는 리랭커에서 얻습니다. 그리고 RAGAS 용어로 두 절반을 따로 측정해야 합니다. 검색 지표(올바른 청크를 가져왔는지)와 충실도(모든 주장이 가져온 내용으로 뒷받침되는지)입니다. 둘은 서로 독립적으로 실패하고, 고치는 방법도 다르기 때문입니다.
아무도 언급하지 않는 보안 공백
벡터 유사도는 의미로 순위를 매길 뿐 권한에 대해서는 아무것도 모릅니다. 그래서 단순하게 구현한 RAG는 데이터 유출 엔진이 됩니다. 회사 문서를 접근 메타데이터 없이 하나의 인덱스에 임베딩하면, 어떤 사용자의 질문이든 어떤 문서든 검색해 낼 수 있습니다. 재무 보고서와 인턴의 질문은 벡터 공간에서 가까이 있는 두 점일 뿐이기 때문입니다. 검색 후 필터링도 해결책이 아닙니다. top-k 선택 이후에 금지된 청크를 버리면 그 존재를 노출하는 동시에 top-k 계약도 깨집니다(6개를 요청했는데 2개를 받습니다). 올바른 패턴은 이렇습니다. 각 청크에 ACL 메타데이터를 함께 저장하고, 유사도 순위를 매기기 전에 질문한 사용자에게 허용된 집합으로 검색을 제한합니다. 권한으로 걸러야 하는 것은 화면 표시가 아니라 검색입니다. 코드 탭의 aclUser 매개변수가 바로 이것이며, 데모와 실제로 출시할 수 있는 시스템을 가르는 차이입니다.
일반적인 사용 사례
- 고객 지원 및 지식 채팅: 회사의 실제 문서를 근거로, 출처를 인용하며, 마지막 수집 시점 기준의 최신 정보로 답합니다.
- 비공개 코퍼스 검색: 법률, 의료, 사내 위키 등에서 키워드 검색창으로는 불가능했던 의미 기반 검색을 제공합니다.
- 고객별 어시스턴트: 각 사용자 자신의 데이터만 다루며, ACL 필터링 덕분에 검색이 테넌트 경계를 넘지 않습니다.
- 근거 있는 분석: 모델의 낡은 추측이 아니라 실시간 레코드를 근거로 질문에 답합니다.
- 문서화와 온보딩: 매뉴얼을 즉흥적으로 지어내는 대신 그대로 인용하는 모델입니다.
RAG가 꼭 필요할까? 결정 매트릭스
| 상황 | 선택할 방법 |
|---|---|
| 지식이 공개적이고 안정적이며 모델에 이미 있음 | 일반 프롬프팅 – 파이프라인 불필요 |
| 코퍼스가 프롬프트에 들어가고 거의 바뀌지 않음 | 프롬프트에 그대로 붙여 넣기(프롬프트 캐싱과 함께) |
| 크거나, 비공개이거나, 자주 바뀌는 지식 | RAG – 본래의 영역 |
| 모델이 다르게 동작해야 함 | 파인튜닝(필요하면 RAG와 함께) |
| 답변에 출처 인용이 필수 | RAG – 인용은 자연스럽게 따라옴 |
| 권한이 걸린 다중 사용자 데이터 | ACL 필터링 검색을 적용한 RAG – 필수 |
한계와 트레이드오프
- RAG는 환각을 줄일 뿐, 없애지는 못합니다. 충실도는 가정할 것이 아니라 측정해야 합니다. 모델은 좋은 컨텍스트도 잘못 읽을 수 있습니다.
- 품질은 검색에서 결정됩니다. 청킹, 하이브리드 검색, 리랭킹 같은 엔지니어링 노력의 대부분은 모델보다 앞단에 있으며, 성과도 거기서 나옵니다.
- 임베딩에는 버전이 있습니다. 임베딩 모델을 바꾸면 저장된 모든 벡터를 다시 생성해야 합니다. 대규모 코퍼스를 다시 임베딩하는 일은 본격적인 마이그레이션입니다.
- 구성 요소가 늘어납니다. 수집 파이프라인, 유지해야 할 인덱스, 쿼리마다 추가되는 토큰과 지연 시간은 실제 비용이며, 작고 안정적인 코퍼스라면 그만한 가치가 없을 수 있습니다.
- 권한은 저절로 따라오지 않습니다. ACL 필터링 검색은 보안을 떠받치는 핵심 작업이며, 데모는 건너뛰고 장애가 뒤늦게 다시 일깨워 주는 부분입니다.
Back4app에서의 RAG
Back4app은 관리형 데이터베이스, 자동 생성되는 REST 및 GraphQL API, 인증, 파일 스토리지, Cloud Code 서버리스 함수를 결합한 오픈소스 Backend as a Service(BaaS) 플랫폼입니다. 여기서 “RAG는 for 루프”라는 말은 문자 그대로입니다. 문서는 평범한 객체이고 원본은 파일 스토리지에 보관합니다. 청크 벡터는 그 옆에 배열 필드로 저장되며, 유사도 검색을 위해 인덱싱됩니다. 이미 모든 쿼리를 통제하는 ACL이 추가 비용 없이 검색의 권한 필터가 됩니다. 사용자가 다른 사람의 레코드를 읽지 못하게 막는 바로 그 규칙이, 검색기가 그 레코드를 가져오는 것도 막습니다. 그리고 임베딩, 검색, 보강, 생성으로 이어지는 오케스트레이션은 키를 서버 측에 둔 채 모델의 API를 호출하는 Cloud Code 함수이며, 코드 탭이 보여 주는 그대로입니다. 프레임워크도, 따로 보안을 챙겨야 할 별도의 벡터 서비스도, 디바이스에 놓인 LLM 키도 없습니다. RAG는 AI 프로젝트가 아니라 이미 할 줄 아는 백엔드 엔지니어링이 됩니다.
자주 묻는 질문
RAG를 쉽게 설명하면 무엇입니까?
앱이 지식 베이스에서 관련 문서를 검색해 LLM의 프롬프트에 붙여 넣는 기법입니다. 그 결과 답변은 모델의 고정된 학습 기억이 아니라 실제 데이터, 최신 데이터, 비공개 데이터에 근거하게 됩니다. 일반 LLM이 책을 덮고 치르는 시험이라면, RAG는 책을 펴 놓고 치르는 오픈북 시험입니다.
RAG를 왜 사용합니까?
재학습 없이 LLM의 세 가지 공백을 한 번에 메웁니다. 환각(검색한 텍스트에 답변을 그라운딩), 학습 데이터 기준일(최신 데이터를 가져옴), 그리고 모델이 사용자의 비공개 문서를 본 적이 없다는 점입니다. 게다가 출처가 알려져 있으므로 답변에서 그 출처를 인용할 수 있습니다.
RAG는 어떻게 작동합니까?
두 단계로 작동합니다. 오프라인 단계에서는 문서를 불러와 청크로 나누고, 각 청크를 벡터로 임베딩해 인덱스에 저장합니다. 온라인 단계에서는 사용자의 질문을 임베딩하고, 가장 유사한 청크를 검색해 프롬프트에 추가한 다음, 모델이 그 컨텍스트에 근거한 답변을 생성하게 합니다.
RAG와 파인튜닝의 차이는 무엇입니까?
바꾸는 대상이 다릅니다. RAG는 사실, 최신성, 비공개 문서 같은 지식을 쿼리 시점에 주입합니다. 파인튜닝은 문체, 형식, 도메인 추론 같은 동작을 학습 단계에서 모델에 새겨 넣습니다. 파인튜닝은 어떻게 답할지를 가르치고 RAG는 무엇에 대해 답할지를 제공하며, 특화된 어시스턴트는 둘 다 쓰는 경우가 많습니다.
긴 컨텍스트 윈도우가 등장하면 RAG는 쓸모없어집니까?
아닙니다. 백만 토큰 규모의 윈도우가 있어도 모든 것을 밀어 넣으면 쿼리당 비용이 훨씬 커지고 속도도 느려지며, 연구에 따르면 윈도우가 차기 훨씬 전부터 정확도가 떨어집니다. 이른바 "컨텍스트 부패(context rot)"입니다. 최신성과 접근 제어에는 여전히 검색이 필요합니다. 2026년의 기본값은 하이브리드입니다. 관련된 부분 집합을 검색한 다음, 긴 컨텍스트 모델로 그 위에서 추론합니다.
청킹이란 무엇이며 왜 중요합니까?
문서를 검색 가능한 조각으로 나누는 작업이며, 검색 품질은 이 작업에 크게 좌우됩니다. 청크가 너무 크면 관련성이 희석되고 프롬프트 예산을 낭비하며, 너무 작으면 맥락을 잃습니다. 흔히 쓰는 기준은 겹침(overlap)을 둔 수백 토큰 크기이지만, 제목, 문단, 코드 블록 단위로 나누는 구조 인식 분할이 대개 고정 크기보다 낫습니다.
RAG를 쓰면 환각이 사라집니까?
줄어들 뿐, 사라지지는 않습니다. 모델은 여전히 검색된 컨텍스트를 잘못 읽거나, 오래된 구절과 최신 구절을 뒤섞거나, 잘못 검색된 결과를 근거로 자신 있게 답할 수 있습니다. 또한 "RAG 환각"의 대부분은 검색 실패가 모습을 바꾼 것입니다. 엉뚱한 청크를 가져왔으니, 그라운딩된 답변도 엉뚱한 근거에 그라운딩된 것입니다.
사용자가 봐서는 안 되는 문서가 RAG로 유출되는 것을 어떻게 막습니까?
유사도만이 아니라 권한으로 검색을 필터링해야 합니다. 벡터 검색은 의미로 순위를 매길 뿐 누가 무엇을 읽을 수 있는지는 전혀 모르므로, 각 청크에 접근 제어 메타데이터를 함께 저장하고 질문한 사용자가 볼 수 있는 문서로 쿼리를 제한해야 합니다. 이 제한은 top-k 선택 이후가 아니라 이전에 적용해야 합니다.