1. pgvector란?
pgvector는 PostgreSQL에 벡터 타입과 유사도 검색 기능을 추가하는 오픈소스 확장(extension) 이다.

별도의 벡터 전용 데이터베이스를 구축하지 않아도, 이미 사용 중인 PostgreSQL에 `CREATE EXTENSION vector;` 한 줄로 벡터 검색 기능을 쓸 수 있게 된다.
-- 활성화
CREATE EXTENSION IF NOT EXISTS vector;
-- vector 타입 컬럼 생성
CREATE TABLE items (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536) -- 1536차원 벡터
);
-- 유사도 검색
SELECT *, embedding <-> '[0.1, 0.2, ...]'::vector AS distance
FROM items
ORDER BY distance
LIMIT 5;
현재 pgvector 0.8 기준으로 최대 16,000차원까지 지원하고,
AWS RDS PostgreSQL 15 이상에서는 기본 탑재되어 있어 `CREATE EXTENSION`만 실행하면 바로 사용 가능하다.
2. FAISS와 비교 — 언제 pgvector를 선택해야할까?
pgvector를 사용하기 전에는 FAISS(Facebook AI Similarity Search)가 가장 많이 쓰이는 벡터 검색 솔루션이었다.
두 방식의 차이를 이해하면 어떤 상황에서 무엇을 선택할지 명확해진다.
FAISS + S3
FAISS는 벡터를 파일(`.faiss`, `.pkl`)로 저장하고, 검색 시 파일 전체를 메모리에 올려서 탐색한다.
흐름:
텍스트 → 임베딩 → FAISS 인덱스 파일 → S3 업로드
검색:
S3에서 파일 전체 다운로드 → 메모리 로드 → 유사도 계산
| 장점 | 단점 |
| 설치/관리 불필요 (파일만 있으면 됨) | 데이터가 늘어나면 매 검색마다 대용량 파일을 메모리에 로드 |
| 소규모 데이터에서 검색 빠름 | 파일 기반이라 동시 쓰기 불가 (경쟁 조건 발생) |
| S3에 저장하므로 별도 DB 비용 없음 | 기존 RDB 테이블과 JOIN 불가 |
| 별도 인프라 구축 소요 시간 X | 벡터 단건 삭제/수정이 어렵고 전체 재생성 필요 |
pgvector + PostgreSQL
pgvector는 벡터를 일반 DB 컬럼처럼 저장하고, SQL로 검색한다.
흐름:
텍스트 → 임베딩 → PostgreSQL INSERT
검색:
SQL SELECT + 벡터 연산자 → 결과 반환
| 장점 | 단점 |
| 기존 테이블과 SQL JOIN 가능 | PostgreSQL 서버 필요 |
| 단건 INSERT/UPDATE/DELETE 가능 | 초대용량(수천만 건 이상)에서는 전용 벡터 DB보다 느릴 수 있음 |
| 동시 쓰기 지원 (ACID 트랜잭션) | 쿼리 튜닝, 인덱싱 등 성능을 위한 작업 필요 |
| 데이터 증가에도 인덱스로 성능 유지 가능 |
이러한 장단점이 있으니, 데이터의 규모, 고려해야하는 상황 등을 파악하여 적절히 선택하여 사용하면 좋을 것 같다.
3. 설치 방법
AWS RDS PostgreSQL 15+
RDS에는 pgvector가 이미 탑재되어 있다. 활성화만 하면 된다.
-- 해당 데이터베이스에 접속 후 실행 (최초 1회)
CREATE EXTENSION IF NOT EXISTS vector;
-- 설치 확인
SELECT * FROM pg_extension WHERE extname = 'vector';
* 권한 오류가 날 경우 RDS 마스터 계정으로 실행하거나, DBA에게 별도 생성 요청을 해야할 수 있다
* AWS 콘솔에서 파라미터 그룹의 `shared_preload_libraries`에 `vector`를 추가 후 재시작이 필요할 수 있다.
로컬 PostgreSQL
1) Docker
docker run -d \
--name pgvector \
-e POSTGRES_PASSWORD=password \
-p 5432:5432 \
pgvector/pgvector:pg16
2) 직접 설치 (bash)
# macOS
brew install pgvector
# Ubuntu
sudo apt install postgresql-16-pgvector
4. 테이블 설계
기본 구조
pgvector 테이블은 일반 테이블에 `vector(차원수)` 컬럼만 추가한 형태다.
CREATE TABLE items (
id SERIAL PRIMARY KEY,
content TEXT,
embedding vector(1536) -- Titan v1 기준 1536차원
);
FK 설정 여부
FK를 걸면 참조 무결성이 보장되지만, 배치 임베딩 시 원본 테이블에 없는 id가 하나라도 있으면 전체 실패한다. 임베딩 테이블은 대량 INSERT가 잦으므로 FK 없이 애플리케이션 레벨에서 관리하는 방식을 많이 택한다.
-- FK 있는 버전 (무결성 보장, 배치 삽입 시 주의)
item_id INT REFERENCES items(id)
-- FK 없는 버전 (유연, 대량 삽입에 유리)
item_id INT
스키마 설계 패턴
원본 테이블과 임베딩 테이블을 1:1로 매핑하는 패턴이 일반적이다.

검색 시 JOIN으로 원본 데이터와 함께 조회한다.
SELECT i.id, i.title,
e.embedding <-> '[...]'::vector AS distance
FROM item_embeddings e
JOIN items i ON e.item_id = i.id
ORDER BY distance
LIMIT 10;
5. 거리 계산 방식 3가지
pgvector는 세 가지 거리 연산자를 제공한다. 같은 임베딩이라도 어떤 연산자를 쓰느냐에 따라 검색 순위가 달라질 수 있다.
<-> L2 거리 (유클리드 거리)
- 두 벡터를 공간상의 두 점으로 보고, 직선 거리를 잰다.
- 값이 작을수록 가깝다.
SELECT *, embedding <-> query_vector AS distance
FROM items ORDER BY distance LIMIT 5;
FAISS의 기본 방식이 L2라서, FAISS와 동일한 결과를 원하면 이걸 쓰면 된다.
<=> 코사인 거리
- 두 벡터가 얼마나 같은 방향을 가리키는지로 유사도를 계산한다.
- 크기는 무시하고 방향만 본다.
SELECT *, embedding <=> query_vector AS distance
FROM items ORDER BY distance LIMIT 5;
값이 0에 가까울수록 유사, 2에 가까울수록 반대 의미다.
<#> 내적 (Negative Inner Product)
- 두 벡터의 내적을 계산한다. 결과가 음수로 나오므로 값이 작을수록(더 음수일수록) 유사하다.
- 벡터가 정규화되어 있으면 코사인과 동일한 결과를 내고, 셋 중 연산이 가장 빠르다.
SELECT *, embedding <#> query_vector AS distance
FROM items ORDER BY distance LIMIT 5;
비교
| 연산자 | 방식 | 크기 반영 | 범위 |
| <-> | L2 (유클리드) | O | 0 ~ ∞ |
| <=> | 코사인 | 0 ~ 2 | |
| <#> | 내적 (음수) | O | -∞ ~ 0 |
6. Python 연동 코드 (psycopg2)
연결 및 기본 쿼리
import os
import psycopg2
import psycopg2.extras
conn = psycopg2.connect(
host=os.getenv("POSTGRESQL_HOST"),
port=int(os.getenv("POSTGRESQL_PORT")),
user=os.getenv("POSTGRESQL_USER"),
password=os.getenv("POSTGRESQL_PASSWORD"),
dbname=os.getenv("POSTGRESQL_DATABASE"),
)
벡터 INSERT
Python 리스트를 그대로 전달하면 psycopg2가 PostgreSQL 배열로 변환한다.
`vector` 타입은 배열로부터 암묵적 변환을 지원하므로 별도 처리 없이 동작한다.
from langchain_aws import BedrockEmbeddings
model = BedrockEmbeddings(model_id="...", region_name="...")
vector = model.embed_documents(["검색할 텍스트"])[0] # list[float]
with conn.cursor() as cur:
cur.execute(
"INSERT INTO item_embeddings (item_id, embedding) VALUES (%s, %s)",
(1, vector) # vector는 list[float] 그대로 전달
)
conn.commit()
벡터 검색
query_vector = model.embed_query("검색어")
with conn.cursor(cursor_factory=psycopg2.extras.RealDictCursor) as cur:
cur.execute(
"""
SELECT e.item_id, e.title,
t.embedding <-> %s::vector AS distance
FROM item_embeddings t
JOIN items e ON t.item_id = e.id
ORDER BY distance
LIMIT %s
""",
(query_vector, 5)
)
results = cur.fetchall()
for row in results:
print(f"거리={row['distance']:.4f} | {row['title']}")
7. JOIN 검색 실전 예시
pgvector의 가장 큰 장점 중 하나는 기존 테이블과 SQL JOIN이 가능하다는 것이다.
기본 JOIN 검색
SELECT
e.id,
e.title,
e.category,
e.status,
t.embedding <=> '[...]'::vector AS distance
FROM item_embeddings t
JOIN items e ON t.item_id = e.id
WHERE e.status = 'active'
ORDER BY distance
LIMIT 10;
조건 필터링 + 벡터 검색
-- 특정 카테고리 안에서 유사도 검색
SELECT e.title, t.embedding <-> query AS distance
FROM item_embeddings t
JOIN items e ON t.item_id = e.id
WHERE e.category = 'tutorial'
ORDER BY distance
LIMIT 5;
유사도 임계값 설정
-- 거리가 0.5 이하인 결과만 반환 (너무 동떨어진 결과 제외)
SELECT e.title, t.embedding <-> query AS distance
FROM item_embeddings t
JOIN items e ON t.item_id = e.id
WHERE t.embedding <-> query < 0.5
ORDER BY distance
LIMIT 10;
이처럼 pgvector는 벡터 검색과 일반 SQL 조건을 자유롭게 조합할 수 있어,
기존 RDB 기반 서비스에 AI 검색 기능을 붙이는 데 매우 적합하다.
'Python' 카테고리의 다른 글
| 벡터 임베딩이란 (0) | 2026.04.17 |
|---|---|
| Video Generation Pipeline 구조 분리 기록 (0) | 2026.03.22 |
| LongCat Avatar: num_segments 자동 계산 로직 추가 (0) | 2026.03.19 |
| pthread_setaffinity_np failed for thread ... Invalid argument. Specify the number of threads explicitly so the affinity is not set. (0) | 2026.03.18 |
| Fish-speech tts 테스트 및 문제 해결(3) (0) | 2026.03.16 |