- Published on
RAG 벡터DB 비용 70% 절감 - PQ·HNSW 튜닝
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
RAG 시스템에서 비용이 가장 빨리 불어나는 지점은 의외로 LLM 토큰이 아니라 벡터DB 인프라인 경우가 많습니다. 문서가 늘수록 인덱스는 커지고, 메모리 상주형 ANN 인덱스(HNSW 등)는 노드 수를 그대로 끌어올립니다. 이 글은 PQ(Product Quantization)로 벡터를 압축하고, HNSW 파라미터를 튜닝해 검색 지연과 리콜을 관리하면서 총비용(TCO)을 70% 수준까지 절감하는 접근을 실전 관점에서 정리합니다.
핵심 메시지는 단순합니다.
- 비용을 좌우하는 것은
dim과N(문서 수)보다도 인덱스 메모리 상주량과 쿼리당 CPU입니다. - PQ는 메모리를 크게 줄이고, HNSW 튜닝은 CPU를 줄입니다.
- “정확도”는 한 숫자로 끝나지 않습니다. RAG에서는 Recall@k 뿐 아니라 **다운스트림(정답률, 근거 적합성)**까지 같이 봐야 합니다.
왜 벡터DB가 비싸지는가: 비용 구성부터 쪼개기
벡터DB 비용은 대략 아래로 분해됩니다.
- 벡터 저장 비용:
N * dim * bytes(FP32면 4바이트) - 인덱스 오버헤드: HNSW 그래프의 링크, 레벨 구조, 메타데이터
- 메모리 상주 비용: HNSW는 성능상 메모리 적중률이 중요해 RAM이 커짐
- 쿼리 CPU 비용: 탐색 폭(
efSearch)과 그래프 복잡도(M)에 비례 - 리빌드/업서트 비용: 인덱스 업데이트, 컴팩션, 리밸런싱
여기서 70% 절감이 나오는 지점은 보통 다음 조합입니다.
- PQ로 벡터 메모리 자체를 4배~16배 줄임
- HNSW
M,efConstruction,efSearch를 재조정해 CPU와 p99 지연을 낮춤 - 그 결과 노드 수를 줄이거나, 동일 노드로 더 많은 컬렉션을 수용
목표 지표 정의: “리콜”만 보면 실패한다
튜닝을 시작하기 전에 아래 지표를 고정하세요.
- 검색 품질
Recall@k(예:k=10,k=20)nDCG@k또는 MRR (순위 품질)
- RAG 품질(다운스트림)
- 정답률(EM/F1), 혹은 LLM 평가 점수
- 근거 적합성(grounding), 인용 문서 정확도
- 성능/비용
- p50/p95/p99 latency
- QPS당 CPU 사용량
- 인덱스 메모리 사용량(GB)
운영 환경에서 429 재시도/백오프가 필요한 경우, 벡터DB가 느려져 상위 레이어에서 재시도가 폭증하며 비용이 2차로 튈 수 있습니다. API 재시도 설계는 별도로 정리한 글도 함께 참고하면 좋습니다: OpenAI 429 RateLimitError 재시도·백오프 구현
PQ(Product Quantization)로 메모리 4~16배 줄이기
PQ가 뭘 줄이는가
PQ는 벡터를 여러 서브벡터로 쪼갠 뒤 각 서브벡터를 코드북 인덱스(바이트)로 저장합니다.
- 원본: FP32
dim=1536이면 벡터 1개당1536*4 = 6144바이트 - PQ 예시:
m=96서브벡터, 각 서브벡터를 8비트 코드로 저장하면96바이트
물론 실제 시스템은 원본 벡터를 일부 보관하거나(리랭킹/재검증), OPQ/IVF를 섞기도 해서 단순 계산과 다를 수 있습니다. 하지만 “벡터 본체”가 차지하는 메모리 비중이 크다면 PQ는 가장 강력한 비용 절감 레버입니다.
PQ 파라미터 감 잡기: m 과 코드 크기
일반적으로 다음이 트레이드오프입니다.
m(서브벡터 개수) 증가: 정확도 상승 경향, 저장 크기 증가- 코드 크기(보통 8bit) 증가: 정확도 상승, 저장 크기 증가
실무에서 자주 쓰는 출발점:
- 임베딩
dim=768:m=48또는m=64 - 임베딩
dim=1536:m=96또는m=128 - 코드 크기: 8bit(256 centroid)
PQ 도입 시 흔한 함정 3가지
- 거리 왜곡: 코사인 유사도를 쓰는데 PQ가 L2 기반으로 학습되면 품질이 흔들릴 수 있습니다. 구현/엔진별로 “내부적으로 정규화 후 L2”로 바꿔치기하는지 확인하세요.
- 롱테일 쿼리 붕괴: 헤드 쿼리는 괜찮은데 희귀 표현에서 Recall이 급락하는 경우가 있습니다. 평가셋을 헤드/롱테일로 분리하세요.
- 리랭킹 부재: PQ는 근사이므로
topK를 넉넉히 뽑고(예: 50~200) 원본 벡터 또는 크로스 인코더로 리랭킹하면 품질 손실을 크게 줄일 수 있습니다.
HNSW 튜닝으로 CPU와 지연을 줄이기
HNSW는 “그래프 기반 근사 최근접 탐색”입니다. 비용 관점에서 중요한 파라미터는 아래 3개입니다.
M: 노드당 링크 수(그래프 밀도)efConstruction: 인덱스 구축 품질(빌드 시간/메모리/정확도)efSearch: 검색 시 후보 탐색 폭(정확도/지연)
M 튜닝: 메모리와 정확도의 스위트 스팟
M이 커지면 그래프가 촘촘해져 Recall은 오르지만- 링크 저장과 탐색 비용이 늘어 메모리/CPU가 증가합니다.
실전 가이드:
- 텍스트 임베딩(768~1536)에서 출발점으로
M=16또는M=24 - 비용이 너무 비싸면
M을 24에서 16으로 내리고, 손실된 Recall은efSearch로 보정 - 반대로 PQ로 거리 정보가 거칠어졌다면
M을 약간 올려 그래프 품질을 보완하는 경우도 있습니다
efSearch 튜닝: 쿼리당 CPU를 직접 제어
efSearch는 “얼마나 넓게 찾아볼지”입니다. 비용 절감에서 가장 즉각적인 손잡이입니다.
efSearch낮춤: 지연/CPU 절감, Recall 하락efSearch높임: Recall 상승, 지연/CPU 증가
운영 팁:
- 쿼리 유형별로
efSearch를 다르게 주는 “적응형”이 효과적입니다.- 예: 짧은 키워드성 쿼리는
efSearch를 높이고 - 명확한 긴 쿼리는
efSearch를 낮춰도 품질이 유지되는 경우가 많습니다
- 예: 짧은 키워드성 쿼리는
efConstruction: 빌드 비용과 운영 비용의 균형
efConstruction은 인덱스 품질에 영향을 주지만, 운영 중 비용(쿼리 CPU)에도 간접적으로 영향을 줍니다. 빌드 품질이 낮으면 같은 Recall을 얻기 위해 efSearch를 더 올려야 해서 운영비가 증가합니다.
- 대규모 컬렉션이면
efConstruction을 너무 낮추지 마세요. - 배치 빌드가 가능하면 빌드 비용을 조금 더 쓰고 운영 비용을 줄이는 편이 TCO에 유리한 경우가 많습니다.
“70% 절감”이 나오는 전형적인 설계 패턴
아래는 자주 재현되는 패턴입니다.
- 원본 FP32 벡터 + HNSW 기본값으로 운영
- PQ 도입으로 벡터 메모리 대폭 감소
- 메모리가 줄어든 만큼 노드 수 축소(또는 동일 노드에 더 많은 샤드 수용)
- PQ로 인한 Recall 손실을
efSearch증가로 보정하되 M을 조정해efSearch를 과도하게 올리지 않게 균형
결과적으로:
- RAM 기반 노드 수가 줄어 월 비용이 크게 감소
- CPU도
efSearch최적화로 줄어들어 autoscaling 규모가 감소
측정 방법: 오프라인+온라인을 같이 돌려라
오프라인 벤치마크(필수)
- 쿼리셋: 프로덕션 로그에서 샘플링(헤드/롱테일 포함)
- 정답셋: 클릭/다운스트림 정답, 또는 고품질 라벨
- 비교 대상: “정확 검색(브루트포스)” 또는 “가장 품질 좋은 설정”
지표:
Recall@10,Recall@20- p95/p99 latency
- 인덱스 크기(디스크/메모리)
온라인 A/B(권장)
RAG는 검색이 좋아져도 LLM이 답을 못하면 의미가 없습니다.
- A/B에서 봐야 할 것
- 사용자 만족 지표(클릭, 재질문율)
- 근거 인용 정확도
- 토큰 비용 변화(검색 품질이 나빠지면 컨텍스트를 더 넣게 되어 토큰이 늘 수 있음)
실전 코드 예제: FAISS로 PQ+HNSW 구성하기
아래 예시는 로컬 벤치마크용으로 많이 쓰는 FAISS 구성입니다. 엔진/서비스형 벡터DB마다 옵션 이름은 다르지만 개념은 동일합니다.
import faiss
import numpy as np
# 예시 데이터
N = 200_000
dim = 1536
np.random.seed(7)
xb = np.random.randn(N, dim).astype('float32')
faiss.normalize_L2(xb) # 코사인 유사도 대체: 정규화 후 inner product
xq = np.random.randn(2000, dim).astype('float32')
faiss.normalize_L2(xq)
# 1) HNSW (inner product)
M = 16
index_hnsw = faiss.IndexHNSWFlat(dim, M, faiss.METRIC_INNER_PRODUCT)
index_hnsw.hnsw.efConstruction = 200
index_hnsw.hnsw.efSearch = 64
index_hnsw.add(xb)
# 2) PQ (IndexPQ는 L2가 기본이라, 실제로는 IVF+PQ 또는 OPQ를 더 자주 씀)
# 여기서는 개념 예시로만 참고
m = 96 # subquantizers
nbits = 8
index_pq = faiss.IndexPQ(dim, m, nbits)
index_pq.train(xb)
index_pq.add(xb)
# 검색
k = 10
D1, I1 = index_hnsw.search(xq, k)
D2, I2 = index_pq.search(xq, k)
print(I1.shape, I2.shape)
현업에서는 보통 아래 형태를 더 많이 씁니다.
IVF+PQ: 대규모에서 속도/메모리 균형OPQ+IVF+PQ: PQ 왜곡을 줄여 Recall 방어- “근사 검색 후 리랭킹”:
topK를 넉넉히 뽑고 재정렬
운영 체크리스트: 튜닝 이후에 터지는 문제들
1) OOM과 재시작 루프
인덱스가 커지거나 efSearch를 올리면 순간 메모리 피크가 증가할 수 있습니다. 쿠버네티스에서 OOM으로 재시작 루프가 나면 성능 튜닝보다 먼저 안정화가 필요합니다.
2) p99 지연이 튄다
대부분 아래 원인입니다.
efSearch가 너무 큼- GC/메모리 압박으로 페이지 폴트 증가
- 샤드 불균형(핫 샤드)
해결은 “리콜을 약간 양보하고 p99를 지키는” 방향이 되는 경우가 많습니다. RAG는 p99가 나빠지면 상위 레이어 타임아웃과 재시도가 겹쳐 시스템이 더 불안정해집니다.
3) 인덱스 업데이트 전략
HNSW는 실시간 업서트가 가능해도, 삭제/업데이트가 누적되면 성능이 흔들릴 수 있습니다.
- tombstone 누적 정책
- 주기적 리빌드/컴팩션
- 배치 머지 전략
이때 배치 작업이 클러스터 리소스를 잡아먹어 서비스가 흔들리면, 오토스케일/우선순위/중단 허용 정책까지 같이 봐야 합니다.
추천 튜닝 플로우(재현 가능한 순서)
- 기준선 측정: 현재 설정에서
Recall@10, p95/p99, 인덱스 메모리 - PQ 도입: 목표 압축률(예: 8배) 설정 후 오프라인 평가
- HNSW
efSearch스윕: 리콜-지연 곡선 작성 - HNSW
M조정: 같은 리콜을 더 낮은efSearch로 달성 가능한지 확인 - 리랭킹 추가(선택): PQ/HNSW 근사로 인한 품질 손실을 회복
- 온라인 A/B: RAG 다운스트림 지표로 최종 승인
결론: 비용 절감은 “압축+탐색”을 같이 잡을 때 나온다
PQ만 도입하면 Recall이 흔들리고, HNSW만 조이면 CPU는 줄지만 메모리 노드 수가 그대로일 수 있습니다. PQ로 메모리를 줄여 노드 수를 내리고, HNSW 튜닝으로 쿼리 CPU와 p99를 관리하면, 품질을 크게 해치지 않으면서도 월 비용을 70% 가까이 절감하는 케이스가 현실적으로 가능합니다.
마지막으로, 튜닝은 한 번에 끝내는 작업이 아니라 데이터 분포(문서/쿼리)가 변할 때마다 재평가해야 합니다. 특히 새로운 도메인 문서가 유입되거나 임베딩 모델이 바뀌면 PQ 코드북과 HNSW 최적점도 함께 이동합니다. 이를 전제로 오프라인 벤치+온라인 A/B 파이프라인을 만들어두면, 비용과 품질을 동시에 지키는 RAG 운영이 훨씬 쉬워집니다.