pgvector로 사내 문서형 RAG 서비스 만들기
메뉴
Moonshot Notes orbit notebook mark
Moonshot NotesAI 도구와 개발 워크플로우 기록하는 공간

AI Backend

pgvector로 사내 문서형 RAG 서비스 만들기

PostgreSQL의 pgvector 확장을 사용해 문서 chunk와 embedding을 저장하고, metadata filter와 vector similarity query로 문서형 RAG 서비스를 구현하는 방법을 정리합니다.

pgvector로 사내 문서형 RAG 서비스 만들기 hero image
Markdown약 1774 tokens

PostgreSQL 안에서 문서 chunk와 embedding 검색하기

이 글에서는 PostgreSQL과 pgvector를 사용해 문서형 RAG 서비스의 기본 검색 구조를 만들어봅니다.

전용 vector DB를 바로 도입할 수도 있지만, 학습과 초기 서비스에서는 PostgreSQL 안에서 벡터 검색을 시작하는 선택이 꽤 실용적입니다. 사용자, 문서, 질문 이력, 색인 상태를 이미 PostgreSQL에 저장한다면 embedding도 같은 DB에서 관리할 수 있기 때문입니다.

분석 기준일: 2026-05-12
실습 기준 환경: PostgreSQL 16+, pgvector, Python, FastAPI
주요 참고자료: pgvector GitHub, Supabase pgvector Docs, RAG Paper

핵심 요약

  • pgvector는 PostgreSQL에서 vector similarity search를 가능하게 하는 확장이다.
  • 문서형 RAG에서는 documents, document_chunks, embeddings, index_jobs 테이블이 기본이다.
  • 검색 쿼리는 vector similarity뿐 아니라 metadata filter와 권한 조건을 함께 적용해야 한다.
  • chunk_id는 citation과 eval에 연결되는 핵심 식별자다.
  • 초기에는 exact search로 시작하고, 데이터가 커지면 index 전략을 검토한다.

1. 왜 pgvector인가

pgvector의 장점은 운영 단순성입니다.

선택지장점주의점
PostgreSQL + pgvector기존 DB와 함께 운영 가능대규모 벡터 전용 최적화는 제한적
전용 vector DB대규모 검색 기능 풍부별도 운영 복잡도
검색 엔진 + vectorhybrid search에 유리학습 비용 증가

초기 학습 프로젝트에서는 pgvector가 좋습니다. SQL로 데이터 모델과 검색을 함께 이해할 수 있기 때문입니다.

2. RAG 데이터 모델

기본 테이블은 다음과 같습니다.

# 예시입니다.documents- id- title- source_type- source_url- version- created_at- updated_at document_chunks- id- document_id- chunk_index- content- token_count- metadata- embedding- created_at index_jobs- id- document_id- idempotency_key- status- error_code- created_at- updated_at

3. pgvector 설치와 확장 활성화

/* 예시 SQL입니다. */CREATE EXTENSION IF NOT EXISTS vector;

embedding 차원이 1536이라고 가정하면 다음처럼 컬럼을 만들 수 있습니다.

/* 예시 SQL입니다. */CREATE TABLE document_chunks (    id UUID PRIMARY KEY,    document_id UUID NOT NULL,    chunk_index INT NOT NULL,    content TEXT NOT NULL,    token_count INT NOT NULL,    metadata JSONB NOT NULL DEFAULT '{}',    embedding vector(1536),    created_at TIMESTAMPTZ NOT NULL DEFAULT now());

embedding 차원은 사용하는 embedding 모델에 맞춰야 합니다.

4. 문서와 chunk 테이블 설계

문서 원본과 chunk는 분리해야 합니다.

테이블역할
documents문서 단위 metadata, version 관리
document_chunks검색 가능한 본문 조각
index_jobs색인 작업 상태 관리
questions사용자 질문 이력
answers모델 답변과 citation 저장

chunk에는 반드시 document_id, chunk_index, content, metadata, embedding이 있어야 합니다.

5. Embedding 저장

embedding 생성 흐름:

# 예시입니다.문서 업로드→ 텍스트 추출→ chunking→ embedding 생성→ document_chunks에 저장

Python 예시:

# 예시 코드입니다.async def index_chunks(document_id: str, chunks: list[str]):    for idx, content in enumerate(chunks):        embedding = await create_embedding(content)        await repo.insert_chunk(            document_id=document_id,            chunk_index=idx,            content=content,            embedding=embedding,        )

6. Similarity Search 쿼리

pgvector에서는 거리 연산자를 이용해 유사한 vector를 찾을 수 있습니다.

/* 예시 SQL입니다. */SELECT    id,    document_id,    content,    metadata,    embedding <-> :query_embedding AS distanceFROM document_chunksORDER BY embedding <-> :query_embeddingLIMIT 5;

거리 값이 낮을수록 더 가깝다는 의미입니다. 사용하는 거리 방식은 embedding 모델과 검색 전략에 맞춰 선택해야 합니다.

7. Metadata Filter와 권한

실서비스에서는 vector similarity만으로 검색하면 안 됩니다. 문서 권한과 범위를 필터링해야 합니다.

/* 예시 SQL입니다. */SELECT    c.id,    c.document_id,    c.content,    c.embedding <-> :query_embedding AS distanceFROM document_chunks cJOIN documents d ON d.id = c.document_idWHERE d.source_type = :source_type  AND d.visibility = 'public'ORDER BY c.embedding <-> :query_embeddingLIMIT :limit;

사용자별 권한이 있으면 ACL 테이블과 join해야 합니다.

8. Citation 연결

RAG 답변에서 citation을 제공하려면 chunk_id가 답변에 연결되어야 합니다.

// 예시 JSON 구조입니다.{  "answer": "Cache Aside는 캐시를 먼저 조회하고 miss 시 DB를 조회하는 패턴입니다.",  "citations": [    {      "document_id": "doc_123",      "chunk_id": "chunk_456",      "quote": "check Redis first, return cached data on a hit..."    }  ]}

citation은 모델이 임의 생성하면 안 됩니다. 검색 결과로 제공된 chunk 목록에서만 선택하게 해야 합니다.

9. 운영 지표와 한계

지표의미
retrieval_latency_ms검색 지연
retrieval_top_k검색 결과 수
retrieval_empty_result_count검색 실패 수
chunk_count_per_document문서별 chunk 수
embedding_generation_latencyembedding 생성 지연
index_job_failure_rate색인 실패율

pgvector는 초기 RAG 구현에 좋지만, 데이터가 커지고 검색 요구사항이 복잡해지면 전용 vector DB, hybrid search, reranker를 검토해야 합니다.

10. 실무 체크리스트

# 예시입니다.[ ] documents와 chunks를 분리했는가?[ ] document version을 저장하는가?[ ] chunk_id가 citation에 연결되는가?[ ] embedding model과 dimension을 기록하는가?[ ] metadata filter를 적용하는가?[ ] 사용자 권한이 retrieval 단계에서 강제되는가?[ ] 색인 job 상태를 추적하는가?[ ] 검색 실패 케이스를 eval dataset으로 저장하는가?

실패 사례: 검색은 되지만 답변 근거를 설명하지 못하는 RAG

pgvector를 붙인 뒤 가장 먼저 만나는 실패는 "비슷한 문서는 찾았는데 답변 품질을 설명할 수 없는" 상태입니다. embedding column과 similarity query만 있으면 demo는 빠르게 됩니다. 사용자가 질문을 보내면 가까운 chunk를 가져오고, 모델은 그 chunk를 바탕으로 답합니다. 하지만 운영자가 "왜 이 문서를 골랐는가", "권한 없는 문서가 섞이지 않았는가", "답변에 사용된 chunk가 정확히 무엇인가"를 묻기 시작하면 단순 vector search는 부족합니다.

실패의 원인은 보통 metadata와 citation 설계가 늦게 들어가기 때문입니다. contentembedding만 저장하면 검색은 가능하지만 문서 버전, tenant, 권한, source URL, chunk 순서, embedding model version을 추적할 수 없습니다. 나중에 hallucination 신고가 들어와도 어떤 chunk가 prompt에 들어갔는지 찾기 어렵습니다.

구현 예시: citation 중심 테이블 설계

RAG table은 답변 생성보다 감사 가능성을 먼저 고려해야 합니다.

create table document_chunks (  id uuid primary key,  document_id uuid not null,  tenant_id text not null,  source_url text,  chunk_index integer not null,  content text not null,  content_hash text not null,  embedding_model text not null,  embedding vector(1536),  created_at timestamptz not null default now()); create index document_chunks_tenant_idx  on document_chunks (tenant_id, document_id);

검색 쿼리도 similarity만 보지 말고 tenant와 문서 상태를 함께 걸어야 합니다.

select id, document_id, source_url, chunk_index, contentfrom document_chunkswhere tenant_id = $1order by embedding <=> $2limit 8;

이 예시는 단순하지만 두 가지 가치를 더합니다. 첫째, 답변에 사용한 chunk id를 그대로 citation으로 남길 수 있습니다. 둘째, 권한 조건을 vector search 바깥이 아니라 같은 query 안에 넣습니다. 실제 서비스에서는 문서 공개 범위, 삭제 상태, 최신 버전 여부도 filter에 포함해야 합니다.

체크리스트 적용 결과

항목확인 질문운영상 의미
권한tenant와 ACL filter가 검색 query에 포함되는가내부 문서 노출 방지
버전embedding model과 content hash를 저장하는가재색인 필요 여부 판단
citationchunk id와 source URL을 답변 로그에 남기는가신고와 eval 재현
품질top-k, threshold, rerank 결과를 측정하는가검색 품질 개선

이 표를 통과하면 "pgvector를 붙였다"에서 "문서형 RAG 서비스를 운영할 수 있다"로 한 단계 올라갑니다.

11. Q&A

Q1. pgvector만으로 충분한가요?

초기 학습과 작은 서비스에는 충분할 수 있습니다. 하지만 대규모 검색, 복잡한 hybrid search, 고성능 요구사항이 생기면 별도 검색 엔진이나 vector DB를 검토해야 합니다.

Q2. chunk 크기는 얼마가 적당한가요?

정답은 없습니다. 문서 유형, 질문 패턴, embedding 모델에 따라 다릅니다. 보통 여러 크기를 실험하고 retrieval eval로 비교해야 합니다.

Q3. embedding 모델을 바꾸면 어떻게 하나요?

기존 embedding과 차원이 다르거나 의미 공간이 다르면 재색인이 필요합니다. embedding_model_version을 반드시 저장해야 합니다.

12. 참고자료와 불확실성

참고자료

불확실성

  • embedding 차원과 거리 연산자는 사용하는 모델에 따라 달라집니다.
  • 인덱스 전략은 데이터 크기와 latency 요구사항에 따라 실험이 필요합니다.

댓글

GitHub 계정으로 로그인하면 댓글을 남길 수 있습니다. 댓글은 GitHub Discussions를 통해 운영됩니다.

TOP