Contents
see List1. pgvector란?
pgvector는 PostgreSQL에 벡터 저장 및 유사도 검색 기능을 추가하는 오픈소스 확장(Extension)입니다. OpenAI, Hugging Face 등의 임베딩 모델이 생성하는 고차원 부동소수점 배열을 그대로 PostgreSQL 컬럼에 저장하고, SQL 쿼리 한 줄로 코사인 유사도, L2 거리, 내적(inner product) 검색을 수행할 수 있습니다.
기존에는 Pinecone, Weaviate, Milvus 같은 전용 벡터 DB를 별도로 운영해야 했지만, pgvector를 사용하면 이미 운영 중인 PostgreSQL 인프라 위에서 벡터 검색을 통합할 수 있어 운영 복잡도를 크게 줄일 수 있습니다.
2026년 현재 최신 버전은 0.8.x이며, 단정밀도(float32) 외에 반정밀도(halfvec, float16), 이진 벡터(bit), 희소 벡터(sparsevec)를 모두 지원합니다. 최대 16,000차원 벡터를 저장할 수 있고 IVFFlat, HNSW 두 종류의 근사 최근접 이웃(ANN) 인덱스를 제공합니다.
2. 설치 및 활성화
Docker (가장 빠른 시작)
# pgvector 내장 공식 이미지
docker pull pgvector/pgvector:pg16
# 컨테이너 실행
docker run --name pgvec \
-e POSTGRES_PASSWORD=secret \
-p 5432:5432 \
-d pgvector/pgvector:pg16Ubuntu / Debian (apt)
# PostgreSQL 16 기준
sudo apt install postgresql-16-pgvectormacOS (Homebrew)
brew install pgvector소스 빌드 (최신 버전이 필요할 때)
git clone https://github.com/pgvector/pgvector.git
cd pgvector
make
sudo make installPostgreSQL에서 확장 활성화
-- 데이터베이스에 접속 후
CREATE EXTENSION IF NOT EXISTS vector;3. 벡터 컬럼 생성 및 데이터 삽입
테이블을 만들 때 vector(N) 타입으로 컬럼을 정의합니다. N은 임베딩 모델의 차원 수와 일치해야 합니다. OpenAI text-embedding-3-small은 1536차원, text-embedding-3-large는 3072차원입니다.
-- 문서 임베딩 테이블 생성 (1536차원)
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
embedding vector(1536),
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- 데이터 삽입 예시 (3차원)
INSERT INTO documents (title, content, embedding)
VALUES
('pgvector 소개', 'PostgreSQL 벡터 확장', '[0.1, 0.2, 0.3]'),
('HNSW 인덱스', '계층적 탐색 알고리즘', '[0.4, 0.5, 0.6]'),
('RAG 시스템', '검색 증강 생성', '[0.7, 0.8, 0.9]');4. 인덱스 종류: IVFFlat vs HNSW
pgvector는 두 가지 ANN(Approximate Nearest Neighbor) 인덱스 알고리즘을 지원합니다. 두 방식 모두 정확한 결과를 보장하지 않지만, 인덱스 없이 전수 검색(exact search)하는 것보다 수십~수백 배 빠릅니다.
IVFFlat (Inverted File with Flat Compression)
데이터를 여러 클러스터(버킷)로 나누고, 쿼리 시 가장 가까운 클러스터 몇 개만 탐색합니다. 빌드 속도가 빠르고 메모리를 적게 사용하지만, 인덱스 생성 전에 충분한 데이터가 존재해야 좋은 품질을 얻을 수 있습니다.
-- IVFFlat 인덱스 생성 (코사인 거리 기준)
-- lists: 100만 행 이하면 rows/1000, 100만 행 초과면 sqrt(rows) 권장
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- 탐색 품질 조절 (기본값 1, 클수록 정확하지만 느림)
SET ivfflat.probes = 10;HNSW (Hierarchical Navigable Small World)
계층적 그래프 구조로 벡터를 연결하여 로그 스케일 탐색을 지원합니다. 빌드 시간과 메모리는 IVFFlat보다 많이 소요되지만, 쿼리 속도와 재현율(recall) 모두 우수하며 데이터가 없는 상태에서도 인덱스를 미리 만들 수 있습니다. 대부분의 프로덕션 환경에서는 HNSW를 권장합니다.
-- HNSW 인덱스 생성
-- m: 각 노드의 최대 연결 수 (기본 16, 높을수록 정확하지만 메모리 증가)
-- ef_construction: 빌드 시 탐색 깊이 (기본 64)
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
-- 쿼리 시 탐색 깊이 조절 (기본 40)
SET hnsw.ef_search = 100;| 항목 | IVFFlat | HNSW |
|---|---|---|
| 빌드 속도 | 빠름 | 느림 |
| 메모리 사용 | 적음 | 많음 |
| 쿼리 속도 | 중간 | 빠름 |
| 재현율(Recall) | 중간 | 높음 |
| 데이터 없이 생성 | 불가 | 가능 |
5. Python + OpenAI Embedding 연동
psycopg2(동기) 또는 asyncpg(비동기)로 PostgreSQL에 연결하고, OpenAI API로 임베딩을 생성하여 삽입하는 전체 흐름입니다.
import psycopg2
from pgvector.psycopg2 import register_vector
from openai import OpenAI
client = OpenAI(api_key='YOUR_OPENAI_API_KEY')
conn = psycopg2.connect(
host='localhost', port=5432,
dbname='mydb', user='postgres', password='secret'
)
register_vector(conn)
cur = conn.cursor()
def get_embedding(text: str) -> list:
res = client.embeddings.create(
model='text-embedding-3-small',
input=text
)
return res.data[0].embedding
def insert_document(title: str, content: str):
embedding = get_embedding(content)
cur.execute(
'INSERT INTO documents (title, content, embedding) VALUES (%s, %s, %s)',
(title, content, embedding)
)
conn.commit()
insert_document('pgvector 튜토리얼', 'PostgreSQL에서 벡터 검색을 사용하는 방법')asyncpg 비동기 방식
import asyncio, asyncpg
from pgvector.asyncpg import register_vector
async def main():
conn = await asyncpg.connect(
host='localhost', port=5432,
database='mydb', user='postgres', password='secret'
)
await register_vector(conn)
embedding = get_embedding('비동기 문서 내용')
await conn.execute(
'INSERT INTO documents (title, content, embedding) VALUES ($1, $2, $3)',
'비동기 예시', '내용', embedding
)
await conn.close()
asyncio.run(main())6. 코사인 유사도 / L2 거리 검색 쿼리
pgvector는 다양한 거리 연산자를 제공합니다. 임베딩 모델이 정규화된 벡터를 반환하는 경우(OpenAI 포함) 코사인 거리와 내적이 동일한 결과를 냅니다.
-- 코사인 거리 기반 유사도 검색 (거리 0에 가까울수록 유사)
SELECT id, title,
1 - (embedding <=> '[0.1, 0.2, ...]'::vector) AS cosine_similarity
FROM documents
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 5;
-- L2 (유클리드) 거리 기반 검색
SELECT id, title,
embedding <-> '[0.1, 0.2, ...]'::vector AS l2_distance
FROM documents
ORDER BY embedding <-> '[0.1, 0.2, ...]'::vector
LIMIT 5;
-- 내적(Inner Product) 기반 검색 (음수로 반환됨)
SELECT id, title,
(embedding <#> '[0.1, 0.2, ...]'::vector) * -1 AS inner_product
FROM documents
ORDER BY embedding <#> '[0.1, 0.2, ...]'::vector
LIMIT 5;
-- 메타데이터 필터와 결합
SELECT id, title
FROM documents
WHERE created_at > NOW() - INTERVAL '7 days'
ORDER BY embedding <=> '[0.1, 0.2, ...]'::vector
LIMIT 10;7. RAG (Retrieval Augmented Generation) 구현 예제
RAG는 사용자 질문을 임베딩으로 변환하여 관련 문서를 벡터 DB에서 검색한 뒤, 검색된 문서를 컨텍스트로 LLM에 전달하는 아키텍처입니다. pgvector를 활용하면 외부 벡터 DB 없이 PostgreSQL 하나로 RAG 파이프라인을 구성할 수 있습니다.
from openai import OpenAI
import psycopg2
from pgvector.psycopg2 import register_vector
client = OpenAI(api_key='YOUR_OPENAI_API_KEY')
conn = psycopg2.connect('postgresql://postgres:secret@localhost/mydb')
register_vector(conn)
cur = conn.cursor()
def retrieve_context(query: str, top_k: int = 5) -> list:
q_embedding = get_embedding(query)
cur.execute(
'SELECT id, title, content, 1-(embedding <=> %s::vector) AS sim '
'FROM documents ORDER BY embedding <=> %s::vector LIMIT %s',
(q_embedding, q_embedding, top_k)
)
return [{'id':r[0],'title':r[1],'content':r[2],'sim':r[3]} for r in cur.fetchall()]
def rag_answer(question: str) -> str:
docs = retrieve_context(question, top_k=3)
context = '
'.join(f'[{d["title"]}]
{d["content"]}' for d in docs)
system_prompt = (
'당신은 친절한 어시스턴트입니다. '
'아래 제공된 문서만을 근거로 질문에 답하세요.
'
f'--- 참고 문서 ---
{context}
---'
)
response = client.chat.completions.create(
model='gpt-4o-mini',
messages=[
{'role':'system','content':system_prompt},
{'role':'user','content':question}
],
temperature=0.2
)
return response.choices[0].message.content
answer = rag_answer('HNSW 인덱스의 장점은 무엇인가요?')
print(answer)8. 성능 튜닝 팁
IVFFlat lists 파라미터 설정
-- 100만 행 이하: lists = rows / 1000
-- 100만 행 초과: lists = sqrt(rows)
-- 탐색 probes는 sqrt(lists)부터 시작
CREATE INDEX ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 1000); -- 100만 행 기준
SET ivfflat.probes = 32; -- sqrt(1000) 약 32HNSW ef_search 조절
-- ef_search 높일수록 재현율 향상, 쿼리 시간 증가
-- 재현율 95% 목표 시 ef_search = 100~200 권장
SET hnsw.ef_search = 100;
-- 트랜잭션 내 세션 단위 설정
BEGIN;
SET LOCAL hnsw.ef_search = 200;
SELECT * FROM documents ORDER BY embedding <=> $1 LIMIT 10;
COMMIT;배치 삽입으로 인덱스 빌드 시간 단축
-- 대량 삽입 시: 데이터 먼저 넣고 인덱스 나중에 생성
SET maintenance_work_mem = '4GB';
COPY documents (title, content, embedding)
FROM '/tmp/embeddings.csv' WITH (FORMAT csv);
-- 삽입 완료 후 인덱스 생성
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops);
-- 병렬 인덱스 빌드 활성화
SET max_parallel_maintenance_workers = 7;반정밀도 벡터로 저장 공간 절반 절감
-- float32 대신 halfvec(float16) 사용: 저장소 50% 절감
CREATE TABLE documents_half (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding halfvec(1536)
);
SELECT id FROM documents_half
ORDER BY embedding <=> '[...]'::halfvec(1536)
LIMIT 5;9. pgvector 0.8.x 최신 기능
반정밀도 벡터 (halfvec)
pgvector 0.7부터 도입된 halfvec 타입은 16비트 부동소수점으로 벡터를 저장합니다. float32 대비 저장 공간과 메모리를 약 50% 절감하면서도 대부분의 임베딩 태스크에서 충분한 정밀도를 유지합니다. 최대 16,000차원을 지원합니다.
희소 벡터 (sparsevec)
BM25, SPLADE 같은 희소 임베딩 모델의 출력을 효율적으로 저장합니다. 대부분의 값이 0인 희소 벡터를 압축 포맷으로 저장하여 공간 효율을 극대화합니다.
-- 희소 벡터 테이블 생성
CREATE TABLE sparse_docs (
id BIGSERIAL PRIMARY KEY,
content TEXT,
embedding sparsevec(30000)
);
-- 삽입: {인덱스:값, ...}/차원수 형식
INSERT INTO sparse_docs (content, embedding)
VALUES ('희소 벡터 예시', '{1:0.5, 100:0.3, 5000:0.8}/30000');
-- L2 거리 검색
SELECT id FROM sparse_docs
ORDER BY embedding <-> '{1:0.5, 100:0.3}/30000'
LIMIT 5;반복 인덱스 스캔 (Iterative Index Scan, 0.8.0)
0.8.0에서 추가된 핵심 기능으로, WHERE 조건 필터링 후 결과가 부족할 경우 인덱스 스캔을 자동으로 확장하여 원하는 개수의 결과를 보장합니다. 메타데이터 필터와 ANN 검색을 함께 사용할 때 발생하던 과도한 필터링(over-filtering) 문제를 해결합니다.
-- HNSW 반복 스캔 활성화
SET hnsw.iterative_scan = relaxed_order;
-- IVFFlat 반복 스캔 활성화
SET ivfflat.iterative_scan = relaxed_order;
-- 필터 + 벡터 검색을 함께 사용해도 안정적인 결과 보장
SELECT id, title
FROM documents
WHERE category = 'tech'
ORDER BY embedding <=> '[...]'::vector
LIMIT 10;이진 벡터 (bit)
벡터를 1비트로 양자화하여 저장하는 bit 타입도 지원합니다. 해밍 거리(Hamming)와 자카드 유사도(Jaccard) 연산에 활용하며, 초고속 유사도 필터링이 필요한 two-stage 검색 파이프라인에 적합합니다.
마치며
pgvector는 PostgreSQL 생태계 안에서 벡터 검색을 완전히 해결해 주는 성숙한 확장입니다. 소규모 프로젝트에서는 IVFFlat으로 가볍게 시작하고, 프로덕션 트래픽이 증가하면 HNSW + halfvec 조합으로 업그레이드하는 전략을 권장합니다. 0.8.x의 반복 스캔 기능은 복잡한 필터 조건이 있는 RAG 시스템에서 특히 강력하게 활용할 수 있습니다.