- Published on
pgvector HNSW 튜닝으로 RAG 검색지연 50% 줄이기
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서빙 환경에서 RAG 지연의 상당 부분은 retrieval에서 발생합니다. 특히 top_k를 10~50으로 올리거나, 동시 요청이 늘어나거나, 문서가 커져서 임베딩 개수가 증가하면 PostgreSQL pgvector의 ANN 인덱스가 병목이 되기 쉽습니다.
이 글은 pgvector의 HNSW 인덱스를 기준으로, 리콜을 크게 떨어뜨리지 않으면서 검색 지연을 약 50% 수준까지 줄이는 튜닝 방법을 “측정 가능한 절차”로 정리합니다. 핵심은 다음 3가지를 동시에 관리하는 것입니다.
- 인덱스 품질을 좌우하는 빌드 파라미터
m,ef_construction - 쿼리 지연과 리콜을 트레이드오프하는 런타임 파라미터
ef_search - RAG 파이프라인 관점에서 필요한
top_k, 필터링, 재랭킹 전략
운영에서 동시에 터지기 쉬운 이슈로는 DB 커넥션 부족도 있습니다. 벡터 검색을 빠르게 만든 뒤 동시성이 올라가면 커넥션이 먼저 한계가 될 수 있으니, 필요하면 RDS PostgreSQL too many connections 원인·해결도 함께 점검하세요.
전제: HNSW가 느려지는 대표 패턴
HNSW는 그래프 기반 ANN이라서 대체로 빠르지만, 다음 조건에서 지연이 급증합니다.
ef_search가 과도하게 큼- 리콜을 올리려고 무작정 키우면 후보 탐색량이 늘어 지연이 선형에 가깝게 증가합니다.
- 필터 조건이 비효율적
WHERE tenant_id = ...같은 필터가 인덱스 탐색과 잘 결합되지 않으면, “후보를 뽑고 버리는” 비용이 커집니다.
top_k가 크고 재랭킹이 없음- LLM 컨텍스트 구성 때문에
top_k를 크게 잡아두면, retrieval 단계에서 불필요한 비용이 발생합니다.
- LLM 컨텍스트 구성 때문에
- 인덱스 빌드 파라미터가 데이터 규모에 비해 보수적
m이 너무 작으면 그래프 연결성이 약해져 탐색이 비효율적일 수 있습니다.
이제부터는 “감으로 조정”하지 않고, 리콜 목표를 정한 뒤 m, ef_construction, ef_search를 순서대로 맞추는 방식으로 접근합니다.
1) 기준선 만들기: 지연과 리콜을 함께 측정
튜닝은 반드시 측정 가능한 기준선이 있어야 합니다.
- 지연:
p50,p95,p99 - 리콜: 오프라인에서
exact검색 대비HNSW의top_k포함률
쿼리 지연 측정: EXPLAIN ANALYZE
아래 예시는 코사인 거리 기준입니다. 연산자와 오퍼레이터 클래스는 환경에 맞게 사용하세요.
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, content
FROM documents
WHERE tenant_id = 't1'
ORDER BY embedding <=> $1
LIMIT 10;
여기서 확인할 포인트는 다음입니다.
Index Scan using ...형태로 HNSW 인덱스를 타는지Buffers에서shared hit대비read비율이 높은지Sort가 발생하는지, 혹은LIMIT가 잘 적용되는지
리콜 측정: exact 대비 포함률
리콜을 측정하려면 “정답”이 필요합니다. 작은 샘플에 대해서는 exact 검색을 돌려 기준을 만들 수 있습니다.
- exact:
ORDER BY embedding <=> $1를 인덱스 없이 수행하거나, 작은 샘플 테이블에서 수행 - ANN: HNSW 인덱스를 탄 결과
오프라인 평가에서는 예를 들어 1,000개 쿼리에 대해 top_10 기준 리콜을 계산합니다.
recall@10 = ANN_top10에exact_top10이 포함되는 비율
리콜 목표를 먼저 정하세요.
- 고객-facing 검색:
recall@100.90~0.97 정도를 흔히 목표로 둠 - 내부 RAG: 재랭킹이 있다면 retrieval 리콜을 조금 낮추고 지연을 더 줄이는 전략도 가능
2) HNSW 인덱스 생성: m·ef_construction의 역할
HNSW는 인덱스를 만들 때 그래프를 구성합니다. 이때 품질을 결정하는 대표 파라미터가 m과 ef_construction입니다.
m: 노드당 연결 수에 가까운 값- 커질수록 연결성이 좋아져 탐색이 쉬워질 가능성이 큼
- 대신 인덱스 크기와 빌드 시간이 증가
ef_construction: 인덱스 빌드 시 후보 탐색 폭- 커질수록 품질이 좋아지는 경향
- 대신 빌드 시간이 증가
추천 출발점
데이터 규모와 차원 수에 따라 다르지만, 운영에서 자주 쓰는 출발점은 다음입니다.
m = 16또는m = 32ef_construction = 100~200
문서가 수백만 건 이상이거나, 리콜을 높게 유지해야 한다면 m을 올리는 쪽이 효과가 좋은 경우가 많습니다. 단, 메모리와 디스크를 같이 봐야 합니다.
인덱스 생성 예시
pgvector에서 HNSW 인덱스 생성은 대략 아래처럼 진행합니다.
CREATE INDEX CONCURRENTLY documents_embedding_hnsw
ON documents
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);
CONCURRENTLY는 운영 중 생성 시 유용하지만 시간이 더 걸릴 수 있습니다.- 오퍼레이터 클래스는
vector_cosine_ops,vector_l2_ops,vector_ip_ops중 거리 정의에 맞게 선택합니다.
3) 런타임 튜닝의 핵심: ef_search로 지연을 깎는다
ef_search는 쿼리 시 탐색 폭입니다.
- 값이 커지면 리콜은 올라가지만 지연도 증가
- 값이 작아지면 지연은 줄지만 리콜이 떨어질 수 있음
튜닝의 목표는 “리콜 목표를 만족하는 최소 ef_search”를 찾는 것입니다.
세션 단위로 ef_search 적용
pgvector는 보통 세션 파라미터로 ef_search를 설정합니다.
SET LOCAL hnsw.ef_search = 40;
SELECT id, content
FROM documents
WHERE tenant_id = 't1'
ORDER BY embedding <=> $1
LIMIT 10;
실전 절차는 다음이 가장 안전합니다.
ef_search를 20, 40, 80, 120처럼 계단식으로 변경- 각 값에서
p95지연과recall@k를 함께 기록 - 리콜 목표를 만족하는 가장 작은
ef_search선택
많은 케이스에서 ef_search를 80에서 40으로 낮추는 것만으로도, 리콜 하락을 제한하면서 지연이 크게 줄어듭니다. “50% 감소” 같은 결과는 보통 여기서 나옵니다.
4) RAG 관점 최적화: top_k, 필터, 재랭킹
DB 인덱스만 만져서는 한계가 있습니다. RAG는 retrieval 이후에도 토큰 비용과 모델 응답시간이 이어지기 때문에, retrieval의 목표는 “정답이 들어있는 후보를 최소 비용으로 확보”입니다.
top_k를 줄이고 rerank로 보완
top_k = 20으로 넉넉히 가져오던 것을top_k = 8~12로 줄이면 DB 지연이 즉시 내려갑니다.- 대신 cross-encoder rerank 또는 lightweight rerank를 붙여 정답률을 유지합니다.
재랭킹을 붙이면 ef_search를 조금 더 낮춰도 전체 품질이 유지되는 경우가 많습니다.
필터링 전략: tenant·시간·카테고리
WHERE tenant_id = ... 같은 필터가 강하게 작동하는 멀티테넌트에서는 특히 중요합니다.
- 가능한 한 후보 집합을 줄인 뒤 벡터 검색이 되도록 설계
- 테넌트별로 테이블 파티셔닝, 또는 테넌트별 인덱스 분리도 고려
다만 PostgreSQL 플래너가 벡터 인덱스와 필터를 항상 이상적으로 결합해주지는 않습니다. 다음을 점검하세요.
- 필터 컬럼에 B-tree 인덱스가 있는지
- 통계가 최신인지
ANALYZE documents;
하이브리드 검색: BM25 또는 키워드 프리필터
벡터 검색 전에 키워드 기반으로 후보를 1차로 줄이고, 그 위에서 HNSW를 돌리면 지연과 품질이 동시에 좋아질 때가 많습니다.
- 예:
tsvector로 1차 후보LIMIT 2000을 만든 뒤, 그 집합에서 벡터 정렬
다만 이 방식은 쿼리가 복잡해지고 플래닝이 어려워질 수 있어, 트래픽이 큰 엔드포인트부터 점진 적용을 권장합니다.
5) 운영 체크리스트: 성능이 다시 나빠지는 이유
튜닝 후에도 시간이 지나면 지연이 다시 올라가는 경우가 흔합니다.
(1) 데이터 증가와 인덱스 품질
HNSW는 데이터가 계속 추가되면 “초기 설계 대비” 품질이 변할 수 있습니다.
- 주기적으로 리콜 샘플링
- 대규모 적재 이후에는
REINDEX를 검토
REINDEX INDEX CONCURRENTLY documents_embedding_hnsw;
(2) VACUUM, 통계, 캐시
- 테이블/인덱스가 커지면 캐시 히트율이 떨어지고
read가 늘어납니다. autovacuum튜닝이나ANALYZE주기 점검이 필요합니다.
(3) 커넥션 풀 고갈
retrieval이 빨라지면 동시 쿼리 수가 늘어 DB 커넥션이 먼저 병목이 될 수 있습니다. RDS를 쓴다면 RDS PostgreSQL too many connections 원인·해결를 참고해 풀 크기, pgbouncer, 애플리케이션 타임아웃을 같이 조정하세요.
6) 재현 가능한 튜닝 플로우: 내가 쓰는 실전 템플릿
아래는 “지연 50% 절감”을 목표로 할 때 가장 재현성이 좋았던 순서입니다.
- 기준선 확보
p95,p99,recall@k기록
- 인덱스 빌드 파라미터 확정
m = 16으로 시작, 리콜이 부족하면m = 32ef_construction = 128또는200
ef_search스윕- 20, 40, 60, 80, 120
- 리콜 목표를 만족하는 최소값 채택
- RAG 파이프라인 보정
top_k를 줄이고 rerank로 보완- 필요 시 하이브리드 검색
- 운영 자동화
- 주기적 샘플링으로 리콜/지연 회귀 탐지
이 흐름의 장점은 “인덱스 품질”과 “쿼리 탐색 폭”을 분리해 생각할 수 있다는 점입니다. 인덱스가 나쁘면 ef_search를 올려도 지연만 늘고 리콜이 기대만큼 안 오르는 구간이 생기는데, 그 상황을 빠르게 진단할 수 있습니다.
7) 예시: 튜닝 전후 비교를 글로 남길 때의 포맷
팀 내 공유나 포스트모템에도 유용한 기록 포맷입니다.
- 데이터: 120만 벡터, 768 차원, 멀티테넌트
- 쿼리:
top_k = 10,tenant_id필터 포함
변경 전
m = 16,ef_construction = 64ef_search = 120p95 = 180ms,recall@10 = 0.96
변경 후
m = 32,ef_construction = 128ef_search = 50p95 = 90ms,recall@10 = 0.95
리콜은 거의 유지하면서 p95가 절반 수준으로 감소합니다. 실제로는 워크로드, 필터, 디스크 성능에 따라 수치는 달라지지만, “ef_search를 낮출 수 있는 인덱스 품질”을 먼저 만들어두면 이런 형태의 개선이 자주 나옵니다.
마무리
pgvector의 HNSW 튜닝은 결국 “리콜 목표를 만족하는 최소 탐색 비용”을 찾는 게임입니다.
- 인덱스 품질은
m,ef_construction - 쿼리 비용은
ef_search - RAG 전체 비용은
top_k와 rerank, 필터링 설계
이 3가지를 같은 대시보드에서 보고, ef_search 스윕을 정기적으로 돌릴 수 있게 만들면 검색 지연을 안정적으로 낮출 수 있습니다.
추가로, RAG 품질을 올릴 때는 retrieval 이후 단계도 중요합니다. 체인 구조에서 정답률을 올리는 방법은 CoT 누출 막기 - SCoT·Verifier로 정답률 올리기처럼 “검증 레이어”를 두는 접근도 함께 고려해볼 만합니다.