- Published on
pgvector RAG 인덱스 느림? ivfflat·hnsw 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙 중인 RAG 시스템에서 pgvector를 붙여두면 초반에는 잘 굴러가다가, 데이터가 늘거나 동시성이 올라가는 순간 갑자기 지연이 튀는 경우가 많습니다. 특히 다음 증상이 자주 같이 나타납니다.
- 벡터 검색 쿼리가 간헐적으로 수백 ms에서 수 초까지 치솟음
- 인덱스를 만들었는데도
Seq Scan비슷한 느낌으로 느림 LIMIT 10인데도 CPU가 과하게 타거나 I/O가 늘어남- 필터(테넌트, 문서 타입, 날짜 등)를 붙이면 더 느려짐
이 글은 “왜 느린지”를 EXPLAIN으로 분해하고, ivfflat/hnsw 각각에서 실제로 체감되는 튜닝 포인트를 정리합니다. RAG 전체 비용/구조 관점은 AutoGPT 메모리 폭주? 벡터DB로 비용 절감도 함께 보면 연결이 잘 됩니다.
전제: pgvector 인덱스가 느려지는 대표 원인
- 인덱스 미사용
- 연산자/정렬 형태가 인덱스가 기대하는 패턴이 아님
ORDER BY embedding가 아니라ORDER BY (embedding <-> $q)형태여야 함
- 필터 + ANN의 상호작용 문제
WHERE tenant_id = ...같은 조건이 강하면, ANN 후보군을 많이 뽑아도 필터에서 탈락해 재시도성 비용이 커짐- 특히
ivfflat는 “리스트 몇 개만 탐색”하는데, 필터로 인해 유효 후보가 부족하면 품질/성능 둘 다 흔들림
- 파라미터 기본값이 현재 데이터 규모에 안 맞음
ivfflat:lists가 너무 작거나 큼,probes가 너무 작음hnsw:m,ef_construction,ef_search가 부적절
- 통계/플래너 문제
- 통계가 오래되어 플래너가 잘못된 계획을 선택
- autovacuum이 밀려서 테이블/인덱스 bloat, 캐시 효율 저하
- I/O 및 메모리 설정
- 벡터 인덱스는 랜덤 접근이 많아
shared_buffers,effective_cache_size,work_mem영향이 큼
기본 테이블/쿼리 패턴(정답 형태 먼저)
아래는 문서 청크 RAG에서 흔한 스키마입니다.
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE doc_chunks (
id bigserial PRIMARY KEY,
tenant_id bigint NOT NULL,
doc_id bigint NOT NULL,
chunk_no int NOT NULL,
content text NOT NULL,
embedding vector(1536) NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
-- 필터용 보조 인덱스(중요)
CREATE INDEX ON doc_chunks (tenant_id, created_at);
CREATE INDEX ON doc_chunks (tenant_id, doc_id);
쿼리는 “거리 계산으로 정렬 + LIMIT” 형태가 핵심입니다.
-- cosine distance 예시
SELECT id, doc_id, chunk_no, content,
1 - (embedding <=> $1) AS score
FROM doc_chunks
WHERE tenant_id = $2
ORDER BY embedding <=> $1
LIMIT 10;
ORDER BY가 embedding <=> $1(또는 embedding <-> $1, embedding <#> $1)처럼 pgvector 연산자 기반이어야 인덱스가 제대로 붙습니다.
느린지부터 확인: EXPLAIN으로 “인덱스가 쓰이는지” 보기
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, doc_id, chunk_no
FROM doc_chunks
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 10;
여기서 확인할 포인트:
Index Scan using ...또는Bitmap Index Scan이 보이는지Buffers: shared hit/read에서 read 비중이 과도하지 않은지Rows Removed by Filter가 지나치게 큰지(필터로 후보가 많이 탈락)
만약 벡터 인덱스가 안 잡히면, 우선 “인덱스 타입/연산자 클래스/거리 연산자”가 일치하는지부터 점검해야 합니다.
ivfflat 튜닝: lists/probes가 전부다(거의)
ivfflat 인덱스 생성 기본
코사인 거리 기준 예시:
CREATE INDEX doc_chunks_embedding_ivf
ON doc_chunks
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
ANALYZE doc_chunks;
여기서 lists는 클러스터(버킷) 개수입니다. 너무 작으면 한 리스트에 데이터가 많이 몰려 검색 시 후보가 커지고, 너무 크면 학습/탐색 오버헤드가 커질 수 있습니다.
lists 추천 가이드(경험칙)
정답은 데이터 분포/차원/필터에 따라 달라지지만, 운영에서 시작점으로는 아래가 무난합니다.
- 데이터
N이 수십만 이하:lists = 100~1000 - 데이터
N이 백만 단위:lists = 1000~5000
너무 러프하다고 느껴질 수 있는데, ivfflat은 결국 probes로 “몇 개 리스트를 열어볼지”를 조정하면서 정밀도와 지연을 트레이드오프합니다.
probes: 쿼리마다 조절 가능한 가장 강력한 레버
SET LOCAL ivfflat.probes = 5;
SELECT id, doc_id
FROM doc_chunks
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 10;
probes가 작을수록 빠르지만 recall이 떨어질 수 있음probes가 클수록 느리지만 recall이 올라감
실전 팁:
- RAG에서는 “top-10을 완벽하게”보다 “top-10이 의미 있게”가 중요한 경우가 많습니다.
- 따라서
probes를 무작정 키우지 말고, **정답 문서 포함률(offline eval)**과 지연을 같이 보고 결정하세요.
필터가 강하면 ivfflat이 더 느려질 수 있다
WHERE tenant_id = 42가 전체의 1%라면, ivfflat이 뽑은 후보 대부분이 다른 테넌트일 수 있습니다. 그러면 Rows Removed by Filter가 커지고, LIMIT 10을 채우기 위해 더 많은 후보가 필요해집니다.
대응 전략:
- 테넌트별 파티셔닝(강력)
CREATE TABLE doc_chunks_t42 PARTITION OF doc_chunks
FOR VALUES IN (42);
-- 파티션별로 벡터 인덱스 생성
CREATE INDEX ON doc_chunks_t42
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 200);
테넌트별 테이블 분리(운영 복잡도 상승)
후보군 확장:
probes를 키워 필터 통과 후보를 늘리기(지연 증가)
ivfflat은 “인덱스 생성 후 데이터 대량 변경”에 취약할 수 있음
대량 적재 후 인덱스를 만들거나, 적재 패턴이 바뀌었으면 아래를 습관처럼 붙이세요.
VACUUM (ANALYZE) doc_chunks;
REINDEX INDEX doc_chunks_embedding_ivf;
특히 RAG는 주기적으로 문서를 재임베딩/재수집하는데, 이때 bloat와 통계 부정확성이 체감 지연으로 이어질 수 있습니다. 락/대기까지 같이 보이면 PostgreSQL 락 대기 폭증? deadlock 진단·해결도 같이 점검하세요.
hnsw 튜닝: m/ef_search/ef_construction의 균형
hnsw는 보통 ivfflat보다 recall 대비 지연이 안정적이고, 필터가 약한 전역 검색에서 강한 편입니다. 대신 인덱스 크기와 빌드 비용이 커질 수 있습니다.
hnsw 인덱스 생성
CREATE INDEX doc_chunks_embedding_hnsw
ON doc_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
ANALYZE doc_chunks;
m: 그래프에서 노드당 연결 수(대체로 8~32 범위에서 시작)ef_construction: 인덱스 구축 품질(클수록 빌드 느림/인덱스 커짐/검색 recall 개선)
ef_search: 쿼리 지연과 recall의 핵심
SET LOCAL hnsw.ef_search = 40;
SELECT id, doc_id
FROM doc_chunks
WHERE tenant_id = 42
ORDER BY embedding <=> $1
LIMIT 10;
ef_search를 올리면 recall이 좋아지지만 지연이 증가- 운영에서는
LIMIT이 10~50인 경우가 많으니,ef_search를LIMIT보다 약간 큰 값에서 시작해 점진 조정하는 방식이 안전합니다.
hnsw가 느린데 CPU가 높다면
ef_search가 과도하게 큰지 확인- 동시 쿼리가 많다면 커넥션/워크로드 제어(풀링, 큐잉)로 tail latency를 줄이는 게 더 효과적인 경우가 많습니다.
RAG에서 자주 터지는 “인덱스는 빠른데 전체 쿼리는 느림” 패턴
벡터 검색 자체는 빨라도, 아래 때문에 전체가 느려집니다.
1) 불필요한 컬럼을 먼저 읽는다
content는 길고 TOAST로 빠질 수 있어 I/O가 커집니다. 먼저 id만 뽑고 조인으로 내용을 가져오면 개선되는 경우가 많습니다.
WITH topk AS (
SELECT id
FROM doc_chunks
WHERE tenant_id = $2
ORDER BY embedding <=> $1
LIMIT 10
)
SELECT c.id, c.doc_id, c.chunk_no, c.content
FROM doc_chunks c
JOIN topk t ON t.id = c.id;
2) rerank(재정렬) 단계가 병목
보통 RAG는 ANN으로 top-k를 뽑고, cross-encoder 또는 추가 스코어링으로 rerank합니다. 이때 k를 너무 크게 잡으면 DB는 빨라도 애플리케이션에서 터집니다.
- ANN
LIMIT은 작게(예: 20~100) - rerank는 배치 처리/캐시/타임아웃을 명확히
3) 필터 컬럼 인덱스가 없다
tenant_id, doc_id, created_at 같은 필터가 자주 붙는데 B-tree 인덱스가 없으면, 벡터 인덱스 후보를 가져와도 필터 처리에서 흔들립니다.
운영 튜닝 체크리스트(빠르게 점검)
쿼리/인덱스 정합성
- 거리 연산자와 opclass가 일치하는가
- 코사인:
vector_cosine_ops+<=> - L2:
vector_l2_ops+<-> - inner product:
vector_ip_ops+<#>
- 코사인:
ORDER BY embedding ... LIMIT ...패턴인가EXPLAIN (ANALYZE, BUFFERS)에서 인덱스 스캔이 보이는가
ivfflat
lists가 데이터 규모 대비 너무 작지 않은가SET LOCAL ivfflat.probes = ...로 recall/지연을 맞췄는가- 테넌트 필터가 강하면 파티셔닝을 고려했는가
hnsw
m을 무작정 키우지 않았는가(인덱스 크기/빌드 비용 증가)ef_search가 과도하지 않은가(특히 동시성에서 tail latency 증가)
DB 운영
VACUUM (ANALYZE)가 제때 도는가- bloat 의심 시
REINDEX를 고려했는가 - 캐시/메모리 설정이 워크로드에 맞는가
벤치마크 방법: “recall vs latency”를 수치로 잡기
튜닝은 감으로 하면 끝이 없습니다. 최소한 아래 두 축을 수치화하세요.
- Latency
- p50/p95/p99로 측정(특히 p95/p99)
- 동시성 수준별로 측정(1, 5, 20, 50 등)
- Recall(또는 hit-rate)
- 기준: brute-force(정확 검색) 결과의 top-
k와 ANN top-k의 교집합 비율 - 또는 “정답 문서가 top-
k에 포함되는 비율”
오프라인 평가를 자동화해두면 probes/ef_search를 올릴지 말지 논쟁이 사라집니다. 관련해서 더 깊은 실전 튜닝 흐름은 pgvector RAG 지연↓ - IVF/HNSW 튜닝 실전도 참고하면 좋습니다.
결론: 어떤 상황에 ivfflat vs hnsw?
ivfflat
- 장점: 구조가 단순, 파라미터가 직관적(
lists,probes) - 주의: 강한 필터/테넌트 분리에서 설계(파티셔닝 등)가 없으면 성능이 요동칠 수 있음
- 장점: 구조가 단순, 파라미터가 직관적(
hnsw
- 장점: recall 대비 지연이 안정적인 편, 운영에서 튜닝 성공률이 높음
- 주의: 인덱스 크기/빌드 비용,
ef_search과대 설정 시 CPU/지연 급증
실무에서 가장 흔한 해결 루트는 다음 순서입니다.
EXPLAIN (ANALYZE, BUFFERS)로 인덱스 사용/필터 탈락/TOAST I/O를 확인ivfflat.probes또는hnsw.ef_search를 “필요한 만큼만” 올려 품질을 맞춤- 필터가 강하면 파티셔닝/테넌트 분리로 ANN 후보 낭비를 줄임
VACUUM (ANALYZE)/REINDEX로 통계와 bloat를 관리
이 4단계를 밟으면, 대부분의 pgvector RAG 지연 문제는 재현 가능하게 설명되고, 튜닝도 반복 가능한 작업으로 바뀝니다.