Erdmates Logo
Erdmates
블로그 목록으로
pgvectorRAGPostgreSQLAI

pgvector로 RAG용 벡터 테이블 설계하기: 차원, 인덱스, 필터링까지

PostgreSQL에서 pgvector로 문서 임베딩을 저장할 때의 테이블 구조, 벡터 타입별 차원 제한, HNSW 인덱스, 메타데이터 필터링 설계를 pgvector 0.8 기준으로 정리합니다.

2026년 10월 12일9 분 읽기

왜 PostgreSQL에 벡터를 저장할까

RAG(검색 증강 생성)는 질문과 의미가 가까운 문서 조각을 찾아 LLM에 함께 넘기는 방식입니다. 이때 "의미가 가깝다"를 계산하려고 문서 조각을 숫자 벡터(임베딩)로 바꿔 저장합니다.

pgvector는 PostgreSQL에서 벡터를 저장하고 유사도로 검색하게 해 주는 오픈소스 확장입니다(2026년 10월 1일 0.8.7 출시). 별도 벡터 데이터베이스 대신 PostgreSQL에 두면 문서의 권한·테넌트·수정 시각 같은 기존 데이터와 같은 트랜잭션, 같은 조인으로 다룰 수 있다는 장점이 있습니다. 그만큼 테이블 설계가 검색 품질과 성능을 좌우합니다.

1. 문서와 청크를 나눈 구조

임베딩은 보통 문서 전체가 아니라 일정 길이로 자른 청크(chunk) 단위로 만듭니다. 문서와 청크를 1:N으로 나눕니다.

CREATE EXTENSION IF NOT EXISTS vector;

CREATE TABLE documents (
  id          BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  tenant_id   BIGINT      NOT NULL,
  title       TEXT        NOT NULL,
  source_url  TEXT,
  updated_at  TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE document_chunks (
  id           BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
  document_id  BIGINT NOT NULL REFERENCES documents (id) ON DELETE CASCADE,
  tenant_id    BIGINT NOT NULL,
  chunk_index  INT    NOT NULL,
  content      TEXT   NOT NULL,
  embedding    vector(1536) NOT NULL,
  model        TEXT   NOT NULL,
  embedded_at  TIMESTAMPTZ NOT NULL DEFAULT now(),
  UNIQUE (document_id, chunk_index)
);

설계 포인트입니다.

  • vector(1536)의 차원은 임베딩 모델의 출력 차원과 같아야 합니다. 다른 차원의 벡터는 같은 컬럼에 넣을 수 없습니다
  • model 컬럼: 서로 다른 모델이 만든 벡터는 비교할 수 없습니다. 모델을 바꿀 때 어떤 행을 다시 만들어야 하는지 이 컬럼으로 찾습니다
  • tenant_id를 청크에도 둡니다: 검색할 때 거의 항상 테넌트로 거르므로, 조인 없이 바로 필터링하고 아래의 부분 인덱스·파티셔닝에도 쓸 수 있습니다
  • ON DELETE CASCADE: 문서를 지우면 그 청크와 벡터도 함께 지워져, 삭제한 문서가 검색에 남지 않습니다
  • embedded_at과 문서의 updated_at을 비교하면 원문이 바뀐 뒤 다시 임베딩하지 않은 청크를 찾을 수 있습니다

2. 벡터 타입과 차원 제한

pgvector는 네 가지 타입을 제공하며, 저장할 수 있는 차원과 인덱스를 만들 수 있는 차원이 다릅니다. vector와 halfvec는 16,000차원까지 저장할 수 있지만 인덱스 한도는 그보다 낮습니다.

타입인덱스를 만들 수 있는 한도
vector2,000차원
halfvec (반정밀도)4,000차원
bit (이진)64,000차원
sparsevec (희소)0이 아닌 원소 1,000개 (저장은 16,000개까지)

임베딩이 2,000차원을 넘으면 vector 컬럼에 그대로 인덱스를 만들 수 없습니다. 이때는 반정밀도로 바꾼 표현식 인덱스를 씁니다. 쿼리도 인덱스와 같은 식으로 정렬해야 인덱스를 탑니다.

-- 3072차원 임베딩을 반정밀도로 인덱싱
CREATE INDEX ON document_chunks_large
  USING hnsw ((embedding::halfvec(3072)) halfvec_cosine_ops);

SELECT id, content
FROM document_chunks_large
ORDER BY embedding::halfvec(3072) <=> $1::halfvec(3072)
LIMIT 5;

3. 거리 연산자와 HNSW 인덱스

pgvector의 거리 연산자는 <->(L2), <#>(내적의 음수), <=>(코사인 거리), <+>(L1), 그리고 이진 벡터용 <~>(해밍), <%>(자카드)입니다. 임베딩 모델이 권장하는 거리를 고르고, 인덱스의 연산자 클래스(opclass)와 쿼리의 연산자를 맞춰야 합니다. 인덱스는 ORDER BY가 거리 연산자의 오름차순일 때 쓰입니다.

인덱스 종류는 두 가지입니다.

  • HNSW: 다층 그래프. 검색 성능이 좋지만 빌드가 느리고 메모리를 더 씁니다. 학습 단계가 필요 없습니다
  • IVFFlat: 벡터를 목록으로 나눕니다. 빌드가 빠르고 메모리를 덜 쓰지만 검색 성능은 HNSW보다 낮습니다
-- 코사인 거리 HNSW 인덱스 (기본값: m = 16, ef_construction = 64)
CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops);

-- 검색 시 후보 수 (기본 40). 올리면 재현율↑ 속도↓
SET hnsw.ef_search = 100;

SELECT id, document_id, content
FROM document_chunks
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 5;

HNSW 인덱스는 그래프가 maintenance_work_mem에 들어갈 때 훨씬 빨리 만들어집니다. 큰 테이블에 인덱스를 만들 때는 이 값을 함께 검토하세요.

4. 필터링: 결과가 모자라는 문제와 해결책

근사 인덱스에서는 인덱스를 먼저 탐색한 뒤에 필터가 적용됩니다. pgvector 문서의 예시로, 조건에 맞는 행이 전체의 10%이고 hnsw.ef_search가 기본값 40이면 평균 4개 행만 남습니다. LIMIT 5를 줘도 5개가 안 나올 수 있다는 뜻입니다.

해결책은 상황에 따라 고릅니다.

반복 인덱스 스캔(0.8.0부터). 결과가 충분히 모일 때까지 인덱스를 더 탐색합니다.

SET hnsw.iterative_scan = strict_order;   -- 거리 순서를 정확히 유지
-- 또는
SET hnsw.iterative_scan = relaxed_order;  -- 순서가 조금 어긋날 수 있지만 재현율이 더 좋음

필터 컬럼에 일반 인덱스. 조건이 전체 행의 작은 비율만 고른다면 B-tree 인덱스로 먼저 거르는 편이 낫습니다.

CREATE INDEX ON document_chunks (tenant_id);

자주 쓰는 값이 몇 개뿐이면 부분 인덱스, 값이 많으면 파티셔닝.

-- 특정 테넌트 전용 벡터 인덱스
CREATE INDEX ON document_chunks USING hnsw (embedding vector_cosine_ops)
  WHERE (tenant_id = 42);

값의 종류가 많다면 PARTITION BY LIST (tenant_id)처럼 필터 컬럼으로 파티션을 나누는 방법을 pgvector 문서가 권합니다.

5. 운영 체크리스트

  • 임베딩 모델을 바꿀 때: model 컬럼으로 대상을 고르고, 새 벡터를 다른 컬럼이나 테이블에 만든 뒤 전환하면 검색이 끊기지 않습니다
  • 원문이 바뀔 때: 해당 문서의 청크를 지우고 다시 만들거나, embedded_at < updated_at인 청크를 주기적으로 찾아 갱신합니다
  • 차원 제한 확인: 인덱스가 필요한 컬럼은 타입별 인덱스 차원 한도 안에 있어야 합니다
  • 검색 품질 측정: ef_search·반복 스캔 설정은 정확도와 속도를 맞바꿉니다. 실제 질의로 재현율을 재 보고 정하세요

Erdmates로 설계할 때

RAG 스키마의 핵심은 문서–청크의 1:N 관계, 삭제가 연쇄되는 외래 키, 그리고 필터에 쓰일 테넌트 키입니다. Erdmates에서 이 관계를 ERD로 먼저 그려 두면, 벡터 컬럼을 어느 테이블에 두고 어떤 컬럼으로 거를지 팀이 같은 그림을 보며 정할 수 있습니다.

마무리

pgvector 설계에서 성능과 품질을 가르는 것은 결국 평범한 데이터베이스 설계입니다. 문서와 청크를 나누고, 모델과 갱신 시각을 기록하고, 차원 한도에 맞는 타입을 고르고, 필터 조건에 맞는 인덱스 전략(반복 스캔·부분 인덱스·파티셔닝)을 세우는 것. 이 네 가지를 갖추면 벡터 검색도 기존 데이터와 같은 수준으로 관리할 수 있습니다.

Erdmates로 바로 시작하세요

AI 기반 ERD 에디터로 데이터베이스 설계를 시작하세요. 무료로 사용할 수 있습니다.

무료 데모 체험무료로 시작하기

관련 기사

bigint, UUID v4, UUID v7: 기본 키 고르는 법 (PostgreSQL 18 uuidv7() 포함)

bigint·UUID v4·UUID v7 기본 키의 장단점을 비교하고, PostgreSQL 18에 들어온 uuidv7()로 달라진 점과 상황별 선택 기준을 정리합니다.

AI 에이전트가 읽는 데이터베이스: MCP 시대의 스키마 설계 원칙

AI 에이전트가 MCP로 데이터베이스에 직접 접속하는 일이 늘고 있습니다. 에이전트가 스키마를 잘못 해석하지 않도록 이름·주석·제약 조건을 설계하고, 권한을 안전하게 나누는 방법을 정리합니다.

PostgreSQL 19 미리보기: 스키마 설계와 운영에 영향을 주는 변화 (베타 4 기준)

정식 출시를 앞둔 PostgreSQL 19의 변경 사항 가운데 테이블 설계·마이그레이션·운영에 직접 영향을 주는 것을 공식 릴리스 노트(베타 4) 기준으로 정리합니다. 베타에서 되돌려진 기능과 업그레이드 전 확인할 동작 변화도 함께 봅니다.

Erdmates Logo
Erdmates

AI 기반 ERD 협업 에디터로 자연어로 데이터베이스를 설계하고 팀과 실시간으로 협업하세요.

서비스

  • 기능 소개
  • 요금제
  • 데모
  • SQL → ERD 변환

리소스

  • 블로그
  • 도움말
  • 문의하기

법적 고지

  • 이용약관
  • 개인정보처리방침
  • 제3자 제공
상호: Produx|대표: 최정회
주소: 강원특별자치도 원주시 솔우물2길 4-1 (무실동)
이메일: master@erdmates.com
개인정보관리책임자: 최정회|호스팅 서비스 제공자: Amazon Web Services

© 2026 Produx. All rights reserved.