pgvector 실습 - 로컬 환경에 벡터 DB 구축하기
지난 글에서 Pinecone, Chroma, pgvector를 비교하면서 기존 RDBMS 인프라를 활용할 수 있다는 점에서 pgvector가 백엔드 개발자에게 특히 매력적이라고 정리했습니다. 이번 글에서는 실제로 pgvector를 로컬 환경에 설치하고, 문서를 저장한 뒤 유사도 검색까지 직접 해보겠습니다.
1. Docker로 pgvector 환경 띄우기
별도 설치 없이 Docker로 pgvector가 포함된 PostgreSQL 이미지를 바로 띄울 수 있습니다.
docker run --name pgvector-test \
-e POSTGRES_PASSWORD=postgres \
-p 5432:5432 \
-d pgvector/pgvector:pg16
컨테이너가 정상적으로 떴는지 확인합니다.
docker ps
2. 확장(Extension) 활성화 및 테이블 생성
접속 후 vector 확장을 활성화하고, 임베딩을 저장할 테이블을 만듭니다.
docker exec -it pgvector-test psql -U postgres
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE documents (
id SERIAL PRIMARY KEY,
content TEXT NOT NULL,
embedding VECTOR(1536),
created_at TIMESTAMP DEFAULT now()
);
VECTOR(1536)은 OpenAI text-embedding-3-small 모델 기준 차원 수입니다. 사용하는 임베딩 모델에 맞춰 차원 값을 바꿔주면 됩니다.
3. Python으로 문서 임베딩 후 저장하기
이제 실제 문서를 임베딩으로 변환해서 테이블에 저장하는 코드를 작성합니다.
import psycopg2
from openai import OpenAI
client = OpenAI()
conn = psycopg2.connect(
host="localhost", port=5432,
dbname="postgres", user="postgres", password="postgres"
)
cur = conn.cursor()
documents = [
"입사 1년 미만 직원은 월 1일의 연차가 발생합니다.",
"입사 1년 이상 직원은 연 15일의 연차가 부여됩니다.",
"재택근무는 팀장 승인 하에 주 2회까지 가능합니다."
]
for doc in documents:
response = client.embeddings.create(
model="text-embedding-3-small",
input=doc
)
embedding = response.data[0].embedding
cur.execute(
"INSERT INTO documents (content, embedding) VALUES (%s, %s)",
(doc, embedding)
)
conn.commit()
cur.close()
conn.close()
4. 인덱스 생성으로 검색 속도 확보하기
지난 글에서 다룬 HNSW 인덱스를 pgvector에서도 그대로 사용할 수 있습니다.
CREATE INDEX ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);
- m : 각 노드가 유지하는 연결 수. 값이 클수록 정확도는 높아지지만 인덱스 크기와 구축 시간이 늘어납니다.
- ef_construction : 인덱스를 만들 때 얼마나 넓게 탐색할지 결정하는 값. 값이 클수록 인덱스 품질이 좋아지지만 구축 시간이 늘어납니다.
5. 유사도 검색 실행하기
사용자 질문을 임베딩으로 변환한 뒤, 코사인 거리 연산자(<=>)로 가장 유사한 문서를 조회합니다.
question = "연차 며칠 쓸 수 있어?"
response = client.embeddings.create(
model="text-embedding-3-small",
input=question
)
question_embedding = response.data[0].embedding
cur.execute(
"""
SELECT content, 1 - (embedding <=> %s::vector) AS similarity
FROM documents
ORDER BY embedding <=> %s::vector
LIMIT 3
""",
(question_embedding, question_embedding)
)
for content, similarity in cur.fetchall():
print(f"{similarity:.4f} | {content}")
<=>는 pgvector가 제공하는 코사인 거리 연산자입니다. 값이 작을수록 더 유사하기 때문에, 1 - 거리로 변환해서 유사도 점수처럼 해석했습니다.
6. 실행 결과 예시
0.8721 | 입사 1년 이상 직원은 연 15일의 연차가 부여됩니다.
0.8103 | 입사 1년 미만 직원은 월 1일의 연차가 발생합니다.
0.2214 | 재택근무는 팀장 승인 하에 주 2회까지 가능합니다.질문과 직접 관련된 연차 관련 문서 두 건이 상위로, 관련 없는 재택근무 문서는 하위로 정렬되는 걸 확인할 수 있습니다.
7. 정리
- Docker 이미지 하나로 pgvector 환경을 손쉽게 로컬에 구축할 수 있습니다.
- VECTOR 컬럼과 HNSW 인덱스를 통해 기존 PostgreSQL 테이블 구조 안에서 벡터 검색이 가능합니다.
- 코사인 거리 연산자(<=>)로 질문 임베딩과 저장된 문서 임베딩 간 유사도를 계산해 관련 문서를 찾아낼 수 있습니다.
지금까지 문서 하나하나를 그대로 임베딩했지만, 실제 문서는 보통 페이지 단위, 문단 단위로 길이가 제각각입니다. 다음 글에서는 검색 품질을 좌우하는 핵심 요소인 청킹(Chunking) 전략을 다뤄보겠습니다.
'RAG를 활용한 LLM 서비스 구축' 카테고리의 다른 글
| STEP 9 - LangChain으로 RAG 파이프라인 처음부터 구축하기 (0) | 2026.07.08 |
|---|---|
| STEP 8 - 문서 청킹 전략 (0) | 2026.07.07 |
| STEP 6 - 벡터 DB 선택하기 (0) | 2026.07.06 |
| STEP 5 - 벡터 DB의 필요성 (0) | 2026.07.06 |
| STEP 4 - 임베딩이란 (0) | 2026.07.03 |