- Published on
RAG 검색품질 2배 - Qdrant HNSW 튜닝 실전
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
RAG에서 검색 품질이 체감상 2배 좋아지는 순간은 대개 모델을 바꿨을 때가 아니라, 벡터 인덱스의 근사 탐색이 “제대로” 동작하게 만들었을 때 옵니다. Qdrant는 기본값만으로도 빠르게 시작할 수 있지만, 데이터 분포(임베딩 차원, 코퍼스 크기, 중복/유사 문서 비율)와 트래픽 패턴(동시성, top_k, 필터링)을 고려하지 않으면 HNSW가 정확도와 지연시간 사이에서 엇박자를 내기 쉽습니다.
이 글은 Qdrant의 HNSW를 “감으로” 만지는 대신, 측정 가능한 지표를 세우고, 튜닝 레버를 하나씩 조정해 검색 품질을 끌어올리는 방법을 다룹니다. 하이브리드 검색과 리랭크까지 포함한 전체 RAG 품질 최적화는 아래 글도 함께 보시면 좋습니다.
1) HNSW가 RAG 품질을 깎아먹는 대표 패턴
HNSW는 근사 최근접 탐색(ANN)이라서, 설정이 보수적이면 빠르지만 정답 근처를 못 찾고, 공격적이면 정확하지만 지연시간과 메모리가 급증합니다. RAG에서 흔히 보이는 실패 패턴은 다음과 같습니다.
(1) top_k는 늘렸는데 근본 품질은 그대로
top_k를 20에서 100으로 늘려도 답변이 나아지지 않는 경우가 있습니다. 이때는 “후보를 더 많이” 가져오는 게 아니라, HNSW가 애초에 올바른 후보 근처로 진입하지 못하는 문제일 수 있습니다. 즉 ef_search가 낮거나, 인덱스 품질이 낮거나(m이 너무 작음), 필터링이 탐색을 방해하는 경우입니다.
(2) 필터를 걸면 품질이 급락
Qdrant에서 payload 필터를 자주 쓰면, 탐색 과정에서 후보가 잘리면서 그래프 탐색이 빈약해지는 상황이 생깁니다. 이때는 필터 전략(사전 필터링 vs 후처리), 세그먼트 구성, 또는 ef_search를 재조정해야 합니다.
(3) 인덱싱은 빨랐는데 온라인 정확도가 낮음
인덱스 빌드 시 ef_construct가 너무 낮으면, 그래프 품질이 떨어져서 온라인에서 ef_search를 올려도 회복이 제한됩니다. “서빙 파라미터만 만져서 해결”이 안 되는 케이스입니다.
2) 튜닝 전에: 품질을 수치로 정의하기
“검색 품질 2배”를 말하려면 최소한 아래 중 하나는 측정해야 합니다.
Recall@k: 정답 문서(또는 정답 청크)가 상위k에 포함되는 비율MRR@k: 정답이 얼마나 상위에 위치하는지(순위 기반)nDCG@k: 정답이 여러 개일 때 가중치를 반영한 랭킹 품질- RAG 관점 지표:
Answer EM/F1,faithfulness,citation hit rate등
실전에서는 보통 검색 레벨 지표(Recall/MRR) 를 먼저 튜닝해 바닥을 올리고, 그 다음 리랭크/프롬프트/컨텍스트 구성으로 답변 품질을 올리는 순서가 안정적입니다.
오프라인 평가용 골든셋 만들기
- 질문
q - 정답 문서 id 리스트(또는 정답 청크 id)
- 가능하면 “정답이 아닌데 비슷한 문서”(hard negative)도 함께
이 골든셋이 있어야 ef_search를 올렸을 때 정확도가 실제로 오르는지, 아니면 지연시간만 늘었는지 판단할 수 있습니다.
3) Qdrant HNSW 핵심 파라미터: 무엇이 품질을 바꾸나
Qdrant에서 HNSW 튜닝의 중심은 크게 세 가지입니다.
m: 노드당 연결 수(그래프 밀도)ef_construct: 인덱스 구축 시 탐색 폭(인덱스 품질)ef_search: 검색 시 탐색 폭(정확도 vs 지연시간)
여기에 운영에서 중요한 옵션이 추가됩니다.
on_disk: 메모리 대신 디스크 기반(메모리 절약 vs 지연)full_scan_threshold: 인덱스 대신 스캔으로 전환되는 임계값quantization: 메모리/속도 최적화(정확도 손실 가능)
아래는 “감 잡기”용 직관입니다.
m (그래프 연결 수)
- 올리면: 그래프가 촘촘해져서 정확도/리콜이 오르기 쉽고, 대신 메모리와 인덱스 빌드 비용이 증가
- 내리면: 메모리 절약, 빌드 빠름, 대신 탐색이 끊겨 리콜이 급락할 수 있음
일반적으로 고품질 RAG 검색을 원하면 m을 너무 낮게 두면 안 됩니다. 특히 코퍼스가 크고(수십만~수천만), 유사 문서가 많은(중복/버전 문서) 환경에서는 m이 부족하면 “근처 군집”으로 못 들어갑니다.
ef_construct (인덱스 품질)
- 올리면: 인덱스 구축 시간 증가, 대신 그래프 품질이 좋아져 온라인 리콜 상한이 올라감
- 내리면: 구축은 빠르지만, 온라인에서
ef_search로도 회복이 제한
ef_search (온라인 탐색 폭)
- 올리면: 보통 리콜이 상승, 대신 지연시간과 CPU 사용량 증가
- 내리면: 빠르지만 리콜 하락
RAG에서는 ef_search가 가장 빠르게 효과를 확인할 수 있는 레버라서, 튜닝은 보통 여기서 시작합니다.
4) 실전 튜닝 플로우: “리콜 2배”를 만드는 순서
아래 순서가 실패 확률이 낮습니다.
- 오프라인 골든셋으로
Recall@kbaseline 측정 ef_search를 단계적으로 올려 리콜-지연시간 곡선 확보ef_search를 올려도 리콜이 안 오르면m과ef_construct를 재설계(재인덱싱)- 필터가 많은 경우: 필터 전략/세그먼트/
ef_search분리 적용 - 마지막으로 quantization, on-disk 등 비용 최적화
(A) ef_search부터: 가장 싸고 빠른 실험
다음처럼 ef_search를 32 -> 64 -> 128 -> 256으로 올리며 측정합니다.
- 지표:
Recall@10,MRR@10,p95 latency - 조건 고정: 임베딩 모델, distance,
top_k, 필터 조건, shard/replica
ef_search를 올렸을 때 리콜이 눈에 띄게 오르면, 인덱스 품질은 충분하고 서빙 탐색 폭만 부족했던 것입니다.
(B) ef_search를 올려도 리콜이 안 오르면 m/ef_construct
이 경우는 인덱스 그래프가 빈약할 확률이 큽니다. 특히 아래 조건이면 더 그렇습니다.
- 코퍼스가 커졌는데 초기에 만든 인덱스 설정을 그대로 사용
- 문서가 서로 매우 유사(버전 관리 문서, FAQ 변형, 로그 템플릿)
- 임베딩 차원이 높고(예:
1024), 군집 구조가 복잡
이때는 m을 늘리고 ef_construct를 늘린 뒤 재인덱싱을 고려해야 합니다.
(C) 필터가 많다면: ef_search를 “필터 케이스별로” 다르게
예를 들어 테넌트/권한 필터로 후보 공간이 급격히 줄어드는 경우, 동일한 ef_search라도 실제 탐색이 더 어려워집니다. 실전에서는 다음 전략이 효과적입니다.
- 필터가 강한 요청에는
ef_search를 더 크게 - 필터가 없는 일반 검색에는
ef_search를 적당히
애플리케이션에서 요청별로 search params를 다르게 주는 방식입니다.
5) Qdrant 설정 예시 (컬렉션 생성/튜닝)
아래는 Qdrant HTTP API 기준 예시입니다. (환경에 따라 필드가 다를 수 있으니 버전 문서를 함께 확인하세요.)
컬렉션 생성: HNSW 파라미터 지정
curl -X PUT "http://localhost:6333/collections/rag_chunks" \
-H "Content-Type: application/json" \
-d '{
"vectors": {
"size": 768,
"distance": "Cosine"
},
"hnsw_config": {
"m": 32,
"ef_construct": 256,
"full_scan_threshold": 10000
},
"optimizers_config": {
"default_segment_number": 2
}
}'
m=32는 품질을 우선하는 시작점으로 자주 씁니다(메모리 여유가 있다면).ef_construct=256은 인덱스 품질을 어느 정도 확보하려는 선택입니다.full_scan_threshold는 소규모일 때 풀스캔으로 빠르게 처리하는 정책인데, 데이터 규모/메모리 상황에 맞춰 조정해야 합니다.
검색 시 ef_search 조정
curl -X POST "http://localhost:6333/collections/rag_chunks/points/search" \
-H "Content-Type: application/json" \
-d '{
"vector": [0.01, 0.02, 0.03],
"limit": 10,
"params": {
"hnsw_ef": 128
},
"with_payload": true
}'
여기서 hnsw_ef가 사실상 ef_search 역할입니다. 같은 limit=10이라도 hnsw_ef를 올리면 더 넓게 탐색해 리콜이 증가하는 경향이 있습니다.
필터가 있는 검색: ef_search 상향
curl -X POST "http://localhost:6333/collections/rag_chunks/points/search" \
-H "Content-Type: application/json" \
-d '{
"vector": [0.01, 0.02, 0.03],
"limit": 10,
"filter": {
"must": [
{"key": "tenant_id", "match": {"value": "t-001"}},
{"key": "doc_type", "match": {"value": "policy"}}
]
},
"params": {
"hnsw_ef": 256
},
"with_payload": true
}'
필터가 강할수록 후보 공간이 줄어 “탐색 난이도”가 올라가므로, hnsw_ef를 더 크게 잡아 리콜을 방어합니다.
6) “2배 향상”이 실제로 나오는 조합 예시
현장에서 자주 보는 개선 시나리오는 아래 형태입니다.
- 기존:
m=16,ef_construct=64,hnsw_ef=32 - 개선:
m=32,ef_construct=256,hnsw_ef=128
이 조합이 항상 정답은 아니지만, 다음과 같은 조건에서 체감 개선이 큽니다.
- 문서가 유사한 덩어리로 뭉쳐 있고(정책/가이드/위키), 질문이 애매한 편
- 청크 단위 검색에서 “바로 옆 청크”를 놓치면 답이 틀어지는 구조
- 리랭커가 있더라도 후보 풀 자체가 부실해서 리랭크가 의미가 없던 상태
핵심은 인덱스 품질 상한(m, ef_construct) 과 온라인 탐색 폭(ef_search) 을 같이 올려 “후보 풀의 바닥”을 올리는 것입니다.
7) 운영에서 자주 밟는 함정: 메모리, OOM, 재시작 루프
m과 ef_construct를 올리면 메모리 사용량이 증가합니다. Qdrant를 Kubernetes로 운영한다면, 리소스 리밋을 보수적으로 잡아 둔 상태에서 튜닝을 시작했다가 OOMKilled 로 이어지는 경우가 많습니다.
- 인덱스 빌드/머지/옵티마이저가 돌 때 피크 메모리가 튈 수 있음
- 동시에
hnsw_ef를 올리면 CPU 사용량도 증가해 p95가 악화될 수 있음
운영 장애 트러블슈팅 관점은 아래 글이 접근법이 비슷합니다.
팁을 몇 가지 정리하면:
- 튜닝은 “서빙 트래픽 적은 시간” 또는 “리드 레플리카”에서 먼저
m/ef_construct변경은 재인덱싱이 필요할 수 있으니 롤링 계획 필수- p95/p99 지연시간과 CPU, RSS 메모리, 디스크 IO를 같이 본다
8) 튜닝 체크리스트 (바로 적용용)
아래 체크리스트대로 진행하면 시행착오가 줄어듭니다.
데이터/쿼리 고정
- 임베딩 모델/버전 고정
- chunking 규칙 고정(청크 길이/오버랩)
top_k고정
지표
Recall@k,MRR@kp50/p95 latency,CPU,RSS
파라미터 스윕 순서
hnsw_ef만 올려서 리콜 곡선 확보- 리콜 상한이 낮으면
m상향 - 인덱스 품질이 의심되면
ef_construct상향 후 재인덱싱 - 필터가 강한 케이스에
hnsw_ef분기 적용
합격 기준 예시
Recall@10이0.35 -> 0.70처럼 2배에 가까운 개선- p95가 SLA를 넘지 않도록
hnsw_ef상한 설정
9) 마무리: HNSW 튜닝은 “정답 후보 풀”을 키우는 작업
RAG 품질은 결국 “정답이 포함된 후보 컨텍스트를 LLM에 주었는가”로 귀결됩니다. Qdrant HNSW 튜닝은 그 후보 풀을 키우는 가장 직접적인 방법이고, 특히 hnsw_ef는 빠르게 효과를 확인할 수 있는 레버입니다.
정리하면:
- 빠른 개선은
hnsw_ef부터 - 상한 개선은
m과ef_construct - 필터가 많으면 케이스별
hnsw_ef분기 - 운영에서는 메모리/피크 부하(OOM)까지 함께 설계
다음 단계로는 하이브리드 검색(BM25+벡터)과 리랭커를 얹어 “정답을 더 위로” 끌어올리는 튜닝이 효과적입니다. 위에서 링크한 하이브리드/리랭크 글과 함께 적용하면 검색 품질의 하한과 상한을 동시에 올릴 수 있습니다.