pgvector 실전: PostgreSQL로 벡터 검색을 운영할 때 알아야 할 것
임베딩 검색의 첫 선택지는 전용 벡터 DB가 아니라 pgvector입니다. 이미 PostgreSQL을 쓰는 서비스라면 확장 하나로 벡터 검색이 추가되고, 트랜잭션·조인·백업이 기존 데이터와 한 몸으로 굴러갑니다. 임베딩이 무엇이고 어떤 검색을 가능하게 하는지는 임베딩과 벡터 검색에서 다뤘으므로, 이 글은 그다음, 운영 관점의 실전을 다룹니다. 기준은 pgvector 0.8 계열(PostgreSQL 18 지원)입니다.
기본기: 타입, 연산자, 인덱스 #
CREATE EXTENSION vector;
CREATE TABLE documents (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
content text NOT NULL,
category text NOT NULL,
embedding vector(1536) -- 임베딩 모델의 차원 수와 일치해야 합니다
);
-- 코사인 거리로 가장 가까운 10건
SELECT id, content
FROM documents
ORDER BY embedding <=> '[0.01, -0.02, ...]'::vector
LIMIT 10;거리 연산자는 세 가지가 주력입니다. <=>(코사인 거리), <->(유클리드), <#>(내적의 음수). 텍스트 임베딩은 대부분 코사인 거리(<=>)가 기본값이고, 모델 문서가 지정하는 거리를 따르면 됩니다. 인덱스 없이도 동작하지만 전체 스캔이므로, 수만 행을 넘으면 인덱스가 필수입니다.
인덱스: HNSW가 기본값입니다 #
| 항목 | HNSW | IVFFlat |
|---|---|---|
| 검색 품질(재현율) | 높음 | 파라미터 민감 |
| 검색 속도 | 빠름 | 중간 |
| 빌드 시간·메모리 | 무거움(병렬 빌드 지원) | 가벼움 |
| 데이터 추가 후 품질 | 유지 | 재빌드 없으면 저하(중심점 고정) |
CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops);실무 기본값은 HNSW입니다. IVFFlat은 빌드가 가볍지만 “인덱스 생성 시점의 데이터로 군집 중심을 고정"하는 구조라, 데이터가 계속 늘어나는 서비스에서는 품질이 조금씩 무너집니다. HNSW는 빌드가 무거운 대신(병렬 빌드로 완화) 추가에 강하고 재현율이 안정적입니다. 주의할 점 하나. 이 인덱스들은 근사(ANN) 검색입니다. 100% 정확한 최근접이 아니라 “거의 항상 맞는 이웃"을 빠르게 돌려주는 것이고, 재현율과 속도는 hnsw.ef_search 같은 파라미터로 교환합니다. 인덱스가 실제로 쓰이는지는 여느 때처럼 EXPLAIN으로 확인합니다.
실전 함정: 필터와 함께 쓸 때 #
pgvector의 진짜 가치는 SQL과의 결합입니다. “category가 ‘docs’인 것 중에서 가장 가까운 10건"처럼 벡터 검색과 WHERE를 한 쿼리로 쓸 수 있습니다. 그런데 여기에 대표 함정이 있습니다. 인덱스는 “가까운 후보 N개"를 먼저 뽑고 그 뒤에 필터가 적용되는 구조라서, 필터가 빡빡하면 후보가 다 걸러져 10건을 채우지 못하는 과소 검색이 일어납니다. pgvector 0.8.0부터는 반복 인덱스 스캔(iterative index scan)이 이 문제를 흡수합니다. 결과가 모자라면 인덱스를 더 파고들어 후보를 추가로 가져옵니다.
SET hnsw.iterative_scan = strict_order; -- 필터형 검색의 안전판필터 선택도가 극단적으로 높다면(전체의 0.1% 같은), 애초에 그 부분집합만 담는 부분 인덱스나 테이블 분리가 더 맞는 설계일 수 있습니다.
규모가 커지면: halfvec과 차원 절약 #
1536차원 float 벡터는 행당 약 6KB입니다. 수백만 행이면 벡터만 수십 GB가 되고, 인덱스가 메모리에 안 들어가는 순간 검색이 급격히 느려집니다. 대응 순서는 이렇습니다. 첫째, halfvec(반정밀도)로 저장 크기를 절반으로 줄입니다(halfvec(1536), 재현율 손실은 대부분 미미하고, HNSW 차원 한도도 4000까지 올라갑니다). 둘째, 임베딩 모델 쪽에서 차원을 줄입니다(주요 모델들이 차원 축소 출력을 지원합니다. 임베딩 모델 선택 참조). 셋째, 그래도 인덱스가 메모리를 넘으면 그때가 아래의 갈림길입니다.
전용 벡터 DB가 필요한 시점 #
판단 기준은 단순합니다. 벡터 검색이 서비스의 주 워크로드가 되고, 그 규모가 PostgreSQL 한 대의 메모리·CPU로 안 될 때입니다. 수천만〜수억 벡터, 초당 수천 쿼리, 밀리초 지연 요구 같은 조건이 겹치면 전용 엔진(분산 샤딩, GPU 가속)이 자기 무대를 갖습니다. 반대로 수백만 벡터 이하에 “기존 데이터와 조인하고 트랜잭션으로 묶는” 요구가 있다면, 운영 시스템을 하나 늘리는 비용이 전용 DB의 이득을 상쇄하고도 남습니다. DynamoDB vs RDS에서 다룬 “전용 스토어 추가의 비용” 논리가 여기서도 그대로 적용됩니다.
정리 #
- 이미 PostgreSQL을 쓴다면 벡터 검색의 첫 선택지는 pgvector입니다. 트랜잭션·조인·백업이 한 몸으로 굴러갑니다.
- 인덱스 기본값은 HNSW입니다. IVFFlat은 증분 데이터에서 품질이 저하됩니다. ANN은 근사라는 것, ef_search로 재현율을 교환한다는 것을 기억하세요.
- 필터 결합 검색의 과소 검색 함정은 0.8.0의 반복 인덱스 스캔으로 대응합니다.
- 규모 대응 순서는 halfvec → 차원 축소 → 그래도 안 되면 전용 DB 검토입니다.
- 전용 벡터 DB의 자리는 “벡터가 주 워크로드 + 한 대의 한계 초과"일 때입니다. 그 전까지는 시스템을 늘리지 않는 것이 이득입니다.