- Published on
pgvector RAG 지연↓ - IVF/HNSW 튜닝 실전
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
RAG를 pgvector로 운영하다 보면 초반에는 간단합니다. vector 컬럼 하나 만들고, 코사인 거리로 ORDER BY ... LIMIT k만 해도 잘 돌아갑니다. 문제는 트래픽이 붙거나(동시성), 문서가 쌓이거나(백만 단위), 필터 조건이 늘어날 때(테넌트, 권한, 시간 범위)입니다. 이때 지연의 대부분은 벡터 유사도 검색 단계에서 발생하고, 해법의 대부분은 인덱스 선택과 파라미터 튜닝에 있습니다.
이 글은 pgvector의 대표적인 근사 최근접(ANN) 인덱스인 IVFFlat과 HNSW를 RAG 관점에서 어떻게 선택하고, 어떤 순서로 튜닝하면 지연을 체계적으로 낮출 수 있는지 다룹니다.
먼저: 지연을 “측정 가능한 형태”로 만들기
튜닝은 감으로 하면 끝이 없습니다. 최소한 아래 3가지는 숫자로 확보하세요.
- p50/p95/p99 지연: RAG는 꼬리 지연이 품질에 직결됩니다.
- 리콜(Recall) 목표: 예를 들어 “정확 검색 대비 top-10에서 0.95 이상” 같은 기준.
- 검색 조건의 현실:
tenant_id같은 필터가 항상 붙는지, QPS는 얼마인지,k는 보통 몇인지.
Postgres에서는 EXPLAIN (ANALYZE, BUFFERS)로 계획과 실제 시간을 봅니다. 인덱스가 제대로 타는지, 필터가 인덱스 전/후에 적용되는지, 버퍼 히트율이 어떤지 확인합니다.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 10;
<=>는 코사인 거리(설정에 따라) 연산자입니다. 본문에 부등호가 포함되므로 위처럼 반드시 코드 블록 안에서만 사용하세요.
IVFFlat vs HNSW: 어떤 인덱스를 선택할까
두 인덱스는 “지연을 줄이기 위한 근사 검색”이라는 목표는 같지만, 운영 특성이 다릅니다.
IVFFlat(IVF) 특징
- 빌드가 비교적 단순하고, 메모리 사용이 예측 가능합니다.
- 검색 시
probes로 “얼마나 많은 클러스터를 훑을지”를 조절합니다. - 데이터가 커질수록
lists(클러스터 수) 설계가 중요해집니다. - 대개 대량 데이터에서 안정적인 성능을 내기 쉽습니다.
HNSW 특징
- 보통 낮은 지연과 높은 리콜을 동시에 달성하기 쉽습니다.
ef_search로 검색 품질/지연을 조절하고,m,ef_construction으로 인덱스 품질과 빌드 비용을 조절합니다.- 인덱스가 커지고 업데이트가 잦으면 메모리/유지 비용이 부담될 수 있습니다.
실전 선택 가이드(빠른 결론)
- 읽기 비중이 높고(p95가 중요), 리콜을 높게 유지해야 한다: HNSW부터 고려
- 데이터가 매우 크고, 메모리/저장 비용을 예측 가능하게 가져가고 싶다: IVF부터 고려
- 필터가 강하게 걸려 후보군이 작아진다: IVF가 생각보다 불리할 수 있고, HNSW가 유리한 경우가 많습니다(단, 필터 적용 방식에 따라 다름)
스키마와 기본 쿼리: RAG용 테이블 구성
RAG에서는 최소한 id, content, metadata, embedding, 그리고 필터용 컬럼(예: tenant_id)이 필요합니다.
CREATE TABLE documents (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
content TEXT NOT NULL,
metadata JSONB,
embedding vector(1536) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX documents_tenant_id_idx ON documents (tenant_id);
여기서 중요한 포인트는 “벡터 인덱스만 만들면 끝”이 아니라, 필터 컬럼 인덱스도 같이 설계해야 한다는 점입니다.
IVFFlat 튜닝: lists와 probes의 균형
IVF는 데이터를 여러 리스트(클러스터)로 나눠서, 검색 시 일부 리스트만 탐색합니다.
인덱스 생성
-- 코사인 거리 기준 예시
CREATE INDEX documents_embedding_ivf_idx
ON documents
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 200);
ANALYZE documents;
lists는 클러스터 수입니다. 너무 작으면 후보군이 커져 느려지고, 너무 크면 각 리스트가 너무 잘게 쪼개져probes를 올려야 리콜이 나옵니다.
검색 시 probes 조절
probes는 “몇 개 리스트를 볼지”입니다. 일반적으로 probes를 올리면 리콜은 올라가고 지연도 올라갑니다.
SET ivfflat.probes = 10;
SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 10;
경험칙(정답은 아니지만 출발점)
lists: 대략sqrt(N)근처에서 시작하는 팀이 많습니다(예:N = 1,000,000이면lists를 1000 근처로 시작).probes:1부터 시작해 리콜 목표에 도달할 때까지 올립니다.
튜닝 순서(추천)
lists를 합리적인 값으로 고정(너무 작지 않게)probes로 리콜을 맞춤- 그래도 느리면
lists를 늘리고probes를 낮춰 재탐색
IVF는 “리콜을 맞추기 위해 probes를 과하게 올리면” 결국 거의 전체 탐색에 가까워져 이점이 사라집니다. 이 구간에 들어갔다면 HNSW로 전환을 고려할 타이밍입니다.
HNSW 튜닝: m, ef_construction, ef_search
HNSW는 그래프 기반 ANN입니다. 튜닝은 크게 두 축입니다.
- 인덱스 품질/크기/빌드 비용:
m,ef_construction - 검색 품질/지연:
ef_search
인덱스 생성
CREATE INDEX documents_embedding_hnsw_idx
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
ANALYZE documents;
m: 그래프에서 각 노드가 유지하는 연결 수에 가깝습니다. 올리면 리콜이 좋아지는 경향이 있지만 인덱스가 커지고 빌드/업데이트 비용이 증가합니다.ef_construction: 인덱스 빌드 품질에 영향. 올리면 빌드는 느려지지만 검색 품질이 좋아집니다.
검색 시 ef_search 조절
SET hnsw.ef_search = 40;
SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 10;
ef_search를 올리면 리콜이 올라가고 지연도 증가합니다.- 실무에서는
ef_search를 “리콜 SLA를 맞추는 다이얼”로 두고, 트래픽 피크 때는 낮추는 식의 운영도 합니다(품질과 비용의 트레이드오프를 명시적으로 관리).
RAG에서 특히 중요한 함정: 필터와 ANN의 결합
RAG 검색은 보통 tenant_id, document_type, created_at 같은 조건이 붙습니다. 이때 흔한 문제는 다음입니다.
- ANN 인덱스는 벡터 기준으로 후보를 찾는데, 필터가 강하면 후보가 필터에서 대거 탈락합니다.
- 그 결과 원하는 k개를 채우기 위해 훨씬 더 많은 후보를 봐야 하며, 지연이 튀거나 리콜이 떨어집니다.
대응 전략 1: 필터 선택도를 먼저 파악
예를 들어 테넌트별 데이터가 매우 작다면, 아예 테넌트별 파티셔닝(또는 테넌트별 테이블/인덱스)도 검토할 수 있습니다.
-- 월 단위 파티셔닝 예시(개념)
-- 실제 운영에서는 테넌트/시간 축 중 무엇이 더 중요한지에 따라 설계
대응 전략 2: 후보 수를 늘리는 대신 비용을 통제
- IVF:
probes를 올리는 대신lists를 재조정 - HNSW:
ef_search를 올리되, p95가 무너지지 않도록 상한을 둠
대응 전략 3: 2단계 검색(거친 후보군 + 정밀 재정렬)
ANN으로 top-k가 아니라 top-k'(예: 100)를 뽑고, 그 안에서 추가 스코어링(BM25, 최신성 가중치, 권한)을 적용해 최종 top-k를 만듭니다.
-- 1) ANN으로 후보 100개
WITH candidates AS (
SELECT id, content, metadata, (embedding <=> $1) AS dist
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 100
)
-- 2) 후보 내에서 재정렬(예: 최신성 가중)
SELECT id, content
FROM candidates
ORDER BY dist ASC
LIMIT 10;
운영에서 지연을 더 줄이는 실전 팁
1) 인덱스 빌드/리빌드 전략을 계획
임베딩 드리프트나 모델 교체로 재색인이 필요할 수 있습니다. 벡터 DB에서도 동일한 문제가 있고, pgvector도 예외가 아닙니다. 운영 관점의 재색인/드리프트 이슈는 아래 글의 접근이 그대로 도움이 됩니다.
2) 백오프/큐로 피크 트래픽을 완화
RAG는 보통 “임베딩 생성 + 검색 + LLM 호출”의 합성 지연입니다. 검색이 빨라져도 상류/하류가 병목이면 p95는 그대로입니다. 특히 외부 API 호출이 섞이면 레이트리밋이 지연을 폭발시킵니다.
3) 캐시 무효화로 “느린 재계산”을 줄이기
RAG 결과를 캐시하는 경우(질문 정규화, 쿼리 임베딩 캐시, top-k 결과 캐시) 캐시 무효화가 꼬이면 오히려 불필요한 재검색이 늘어납니다. Next.js 기반이라면 태그 기반 무효화 전략을 함께 보세요.
튜닝 체크리스트: “지연↓”을 재현 가능하게
아래 순서대로 하면 시행착오를 크게 줄일 수 있습니다.
- 기준선 확보: 정확 검색(브루트포스) 대비 리콜, p95 지연, QPS
- 인덱스 선택: IVF 또는 HNSW를 워크로드에 맞게 결정
- 파라미터 1차 세팅
- IVF:
lists고정 후probes로 리콜 맞추기 - HNSW:
m,ef_construction은 보수적으로 시작 후ef_search로 리콜 맞추기
- IVF:
- 필터 영향 측정: 필터가 강할수록 후보 탈락이 늘어나는지 확인
- 2단계 검색 도입 여부 결정: ANN 후보 확대 + 재정렬
- 운영 상한 설정:
probes/ef_search최대치, 타임아웃, 리트라이 정책 - 재색인 플랜: 모델 교체/드리프트/스키마 변경 시 롤링 재색인
결론: IVF/HNSW는 “다이얼”이고, 목표는 p95 안정화
pgvector RAG에서 지연을 줄이는 핵심은 ANN 인덱스를 “켜는 것”이 아니라, 리콜 목표와 필터 현실을 반영해 다이얼을 조정하는 것입니다.
- IVF는
lists와probes의 균형이 전부이고, - HNSW는
ef_search를 중심으로, 필요하면m과ef_construction까지 조정합니다.
마지막으로, RAG의 사용자 체감은 p50이 아니라 p95/p99에서 결정됩니다. 튜닝 결과를 항상 꼬리 지연 기준으로 검증하고, 피크 트래픽에서 파라미터 상한이 어떤 영향을 주는지까지 포함해 운영 설계를 마무리하면 “지연↓”을 안정적으로 달성할 수 있습니다.