혼자 쓰는 RAG: 인증·권한을 빼고 남은 설계 결정들
ChatGPT에 문서를 전부 올리면 끝나는 일 아닌가,라고 한동안 생각했습니다. 그런데 제가 쓰는 데이터를 전부 올리는 건 사정이 달랐습니다. 재고 목록, 영업 리드, 기도 기록, 가계부까지 한 사람의 전 삶이 들어있는 DB였거든요. 그래서 로컬 LLM 기반 RAG를 직접 짰습니다. 이 글은 그 과정에서 내린, 그리고 내리지 않은 결정들을 적은 겁니다.
"혼자 쓴다"의 진짜 의미
제 대시보드는 114개 테이블로 이루어져 있습니다. 성경 읽기 기록, 도서 메모, 북마크, 성가대 일정, 식단, 수면, 운동, 영업 리드, 메일 초안, 비밀번호 관리(Vault)까지 한 사람의 전 영역이 들어있습니다. 그 위에 RAG를 얹었습니다.
여기서 "혼자 쓴다"는 단순히 "사용자가 한 명"이란 뜻이 아닙니다. 인증, 권한, 감사 로그, 멀티테넌시, 사용량 제한, 요금 — 기업용 RAG가 기본으로 달고 다니는 것들을 전부 뺄 수 있다는 뜻입니다. SSO 붙일 필요 없고, 사용자별로 검색 권한을 갈라야 할 일도 없고, 누가 무슨 질문을 했는지 감사 로그를 남기지 않아도 됩니다.
대신 그 자리에 남는 게 있습니다. "내가 묻는 질문에 내가 속으면 안 된다"는 단 하나의 원칙입니다. 이게 전체 설계의 중심이 됐습니다.
색인은 해두되, 답은 코드로 만든다
처음엔 모든 걸 RAG에 통과시키려 했습니다. 재고 질문도 문서로 색인해두고 LLM이 답하게 하려 했죠. "이케아 철제장에 뭐 있어? 표로 정리해줘" 같은 질문을 RAG에 맡겼더니 문제가 생겼습니다.
top-k 검색이 전체 목록을 보장하지 못합니다. 열 개 청크만 가져오니까 물건이 빠집니다. 그리고 모델은 수십 행을 빠뜨리지 않고 전부 옮기는 데 약합니다. 열한 번째 물건이 빠지면 그냥 안 보고 답을 씁니다. 저는 그걸 믿고 물건을 정리하려 했고, 그러면 실제로는 없는 물건이 있다고 착각하게 됩니다.
그래서 재고 질문은 LLM을 거치지 않기로 했습니다. 위치·물건 조회 인텐트를 감지하면 Prisma로 직접 조회해서 마크다운 표를 코드가 만듭니다. 코드에 주석으로 이렇게 적어뒀습니다. "LLM이 데이터를 거치지 않으므로 개수·이름·수량이 틀릴 수 없다."
재미있는 건, 재고(Item) 데이터는 여전히 doc_chunk 테이블에 색인되어 있다는 겁니다. source_type 컬럼이 doc과 item을 구분하고, CHECK 제약으로 "둘 중 정확히 하나"를 강제합니다. 지식 질문("철제장이 뭐야?")은 벡터 검색으로 가고, 목록 질문("철제장에 뭐 있어?")은 직조회로 갑니다. 같은 데이터를 두 경로로 준비해둔 셈입니다. 색인은 남겨두되, 답을 만드는 길만 다르게 뚫었습니다.
모델은 다섯 용도, 실제로는 하나
설정 스키마는 모델을 다섯으로 나눕니다. 답변(chatModel), 리드 텍스트 추출(textModel), 리드 이미지 추출(visionModel), 메일 작성(writingModel), 검색 결과 재정렬(rerankModel). 용도마다 다른 모델을 쓰게 한 이유는, 각 용도에 맞는 크기를 쓰면 GPU를 아낄 수 있기 때문입니다.
코드 주석에 그 의도가 적혀 있습니다. 재정렬 모델은 "후보 20개를 훑고 번호만 고르는 일이라 큰 모델이 필요 없다"고요. 빈 문자열이면 재정렬을 건너뛰게 해뒀습니다.
그런데 실제 설정을 보면 다섯 칸이 전부 같은 모델로 돼 있습니다. qwen3.6:35b-a3b 하나로 통일했습니다. 35B 매개변수에 활성 3B인 MoE 모델입니다. 용도별로 모델을 갈라두면 분명 더 싸게 돌아가겠지만, 혼자 쓰니까 모델 관리 부담이 더 큽니다. 다섯 모델을 각각 최신으로 유지하고, 각각이 살아 있는지 확인하는 게 귀찮았습니다. 그래서 하나의 좋은 모델로 단순화했습니다.
이게 "단순함 우선"이라는 원칙의 실체입니다. 스키마는 유연성을 남겨두되, 운영은 가장 단순한 쪽으로 택합니다.
맥락은 클라이언트 말고 서버에서
RAG 챗봇을 처음 짤 때 빠지기 쉬운 함정이 있습니다. 프론트엔드가 대화 맥락을 들고, 요청마다 서버에 통째로 보내는 구조입니다. 이게 편해 보이지만, 혼자 쓴다고 해도 문제가 생깁니다. 저는 보통 여러 탭을 띄워놓고 씁니다. 한 탭에서 묻고 다른 탭에서 같은 대화를 열면, 두 탭의 맥락이 어긋납니다.
그래서 맥락은 클라이언트가 보낸 값 대신 DB에서 읽게 했습니다. 코드 주석에 적은 대로, "여러 탭에서 같은 대화를 열어도 서버가 본 기록 하나만 근거가 되고, 요청 본문도 가벼워진다"가 그 이유입니다.
이건 기업용 RAG에서 흔히 신경 쓰는 동시성 문제와 비슷해 보이지만, 동기가 다릅니다. 기업용은 "여러 사용자가 같은 데이터를 고칠 때"를 대비하지만, 여기서는 "한 사용자가 여러 탭을 쓸 때"를 대비합니다. 혼자 쓴다고 동시성이 사라지는 건 아니라는 걸, 탭을 열어놓고 쓰다 보니 알게 됐습니다.
실패해도 답은 살린다
스트리밍 답변을 만들다 보면 중간에 끊기는 일이 잦습니다. 네트워크가 불안하거나, 제가 중지 버튼을 누르거나, 모델이 멈추거나. 이때 화면에 보인 답과 DB에 저장된 답이 어긋나면, 나중에 대화 기록을 펼쳤을 때 전혀 다른 답이 나옵니다.
그래서 답변 스트림이 중간에 끊겨도, 그때까지 나온 부분이라도 저장하게 했습니다. 주석에 적은 이유는, "사용자가 읽은 화면과 기록이 어긋나면 안 된다"입니다. 꼬리 출처 목록은 화면과 같은 규칙으로 걷어낸 뒤 저장합니다.
재정렬 단계도 같은 결정으로 되어 있습니다. 작은 모델로 검색 결과를 다시 정렬하다가 실패하면, 원래 순서로 갑니다. 재정렬이 실패했다고 답변 전체가 막히면 안 되니까요. 혼자 쓰는 시스템이지만, 내가 물었을 때 아무 답도 안 오는 것보다는 덜 정렬된 답이라도 오는 게 낫습니다.
혼자 쓰는 RAG와 기업용 RAG의 본질 차이
정리하면, 제가 뺀 것들은 기업용이 당연히 가져야 할 것들입니다. 인증, 권한, 감사, 사용량 제한, 멀티테넌시. 이걸 다 빼니 설계가 놀랍도록 단순해졌습니다.
대신 제가 남긴 것들은 기업용이면 상대적으로 덜 신경 쓸 수 있는 것들입니다. 내가 물은 질문에 내가 속이는 답이 나오지 않게(직조회), 내가 여러 탭을 써도 맥락이 어긋나지 않게(서버 맥락), 답변이 중간에 끊겨도 기록이 화면과 같게(부분 저장). 전부 "혼자 쓰는 사람이 겪는 불편"을 향해 있습니다.
기업용 RAG의 단단함은 주로 겉으로 드러나는 위협(권한 침해, 비용 초과, 규제)을 막는 쪽에 쏠려 있습니다. 반면 혼자 쓰는 RAG의 단단함은, 겉으로 안 보이는 위협(내가 스스로를 속이는 답, 어긋나는 기록, 끊긴 대화)을 막는 쪽에 쏠려 있습니다. 사용자가 한 명이라 신경 쓸 게 적어지는 게 아니라, 신경 쓸 대상이 달라진다는 걸 이 시스템을 짜면서 알게 됐습니다.
114개 테이블 위에 RAG 하나를 얹는 일은, ChatGPT에 올리는 것보다 분명 더 일이 많습니다. 하지만 내가 속지 않는다는 확신과, 내 데이터가 밖으로 나가지 않는다는 확신은, 그 일을 할 만한 충분한 이유가 됐습니다.
함께 읽기
- 한국어 RAG에서 벡터만 쓰면 안 되는 이유: 하이브리드 검색 설계처음 RAG를 짤 때는 벡터 검색만 썼습니다. 코사인 유사도 하나로 충분할 줄 알았죠. 그런데 한국어 질문에서 이상한 일이 잦았습니다. 분명 문서에 있는 단어를 물었는데 검색이 안 됩니다. 비슷한 의미의 다른 문서만 계속 위에 뜹니다. 원인을 찾다 보니, 벡터 검색이 "의미가 비슷한가"는 잘 재는데 "그 단어가 실제로 …
- 문서 RAG에 재고 981건을 같이 색인한 방법: 구조화 데이터의 텍스트화RAG를 "문서 검색"이라고만 생각하면 놓치는 게 많습니다. 제가 실제로 자연어로 묻고 싶은 질문의 절반은 문서가 아니라 데이터베이스에 들어있는 구조화된 데이터에 대한 것이었습니다. "이 철제장에 뭐 있지", "이 브랜드 물건 중 정리 대기 걸린 건 뭐지", "유효기간 지난 거 있어?". 이런 건 문서로 안 적어둡니다.…
- RAG 운영: 전체 재색인을 피하고 캐시는 본 요청을 막지 않는 법처음 RAG를 짤 때는 문서를 고칠 때마다 전체를 다시 색인했습니다. 문서가 백 개쯤 될 때는 그래도 됐습니다. 잠깐 기다리면 끝났거든요. 그런데 문서가 천 개를 넘고, 거기에 재고 아이템까지 색인에 들어가니 얘기가 달라졌습니다. 전체를 다시 임베딩하면 수천 번 모델을 부르는 셈이고, 그 사이에 질문이 들어오면 답이 늦…
- RAG 답변 품질을 메트릭 없이 지키는 법: 출처·임계값·방어 코드RAG 답변 품질을 재는 정량 프레임워크가 있습니다. RAGAS, TruLens 같은 도구가 답변의 충실도(faithfulness), 관련성, 맥락 적합성을 점수로 냅니다. 처음엔 이런 걸 달아야 하나 고민했습니다. 그런데 막상 혼자 쓰는 RAG를 짜고 보니, 정량 점수보다 답이 거짓말을 안 하게 만드는 "코드"가 먼저…
- 멀티모달 RAG의 현실: 모달을 섞지 않고 텍스트로 환원하는 이유멀티모달 RAG라는 말을 처음 들었을 때, 이미지를 임베딩해서 문서 텍스트와 같은 벡터 공간에 올리는 걸 상상했습니다. CLIP 같은 모델로 그림과 글을 하나의 공간에 두면, "이런 느낌의 다이어그램 찾아줘" 같은 질문도 답이 나올 것 같았죠.