벡터 검색 알고리즘 입문에서 HNSW가 kNN을 실시간에 쓰게 만든 표준 알고리즘이라는 걸 봤다. 이 글은 그 다음 단계다 — 파라미터를 어떻게 튜닝하고, 왜 같은 설정인데 제품마다 성능이 다른가.
세 파라미터는 **빌드 시점(1회) vs 검색 시점(매 쿼리)**으로 성격이 갈린다.
각 노드가 유지하는 양방향 링크 수(최하층은 2M). 그래프가 얼마나 촘촘한지를 정한다.
벡터를 삽입할 때 이웃 후보를 몇 개까지 탐색하는지. 그래프 자체의 품질을 정한다.
ef_construction ≥ M.바닥 레이어에서 유지하는 후보 리스트 크기. 질의마다 바꿀 수 있는 유일한 파라미터다.
ef_search ≥ k는 필수다. 후보 리스트가 k보다 작으면 top-k를 반환할 수 없다(Milvus는 아예 ef < k 설정을 막는다).| 파라미터 | 시점 | 올리면 | 바꾸려면 |
|---|---|---|---|
| M | 빌드 | recall↑ 메모리↑ 빌드↑ | 재빌드 |
| ef_construction | 빌드 | recall↑ 빌드↑ (검색 무관) | 재빌드 |
| ef_search | 매 쿼리 | recall↑ QPS↓ | 즉시(재빌드 불필요) |
M과 ef_construction은 역할이 다르다. M은 "이웃을 몇 개 저장할지"(구조·메모리)이고, ef_construction은 "그 이웃을 얼마나 잘 고를지"(탐색 품질)다. M을 키우면 채울 슬롯이 늘어나므로, 좋은 후보로 채우려면 ef_construction도 함께 올려야 효과가 난다. M만 키우고 ef_construction을 낮추면 슬롯이 부실한 이웃으로 채워져 메모리만 쓰고 recall이 안 오른다.
멘탈모델은 이렇다 — M·ef_construction으로 그래프의 "recall 천장"을 정하고, ef_search로 그 천장 안에서 recall-속도 지점을 고른다. 그래서 여러 제품을 공정하게 비교하려면 재빌드 없이 ef_search만 여러 값으로 훑어(sweep) 같은 recall 지점에서 QPS를 비교한다.
HNSW가 "가깝다"를 판정하는 건 결국 벡터 간 거리 계산이고, RAG의 표준인 코사인 유사도는 실제로 내적으로 계산된다.
코사인 유사도 공식은 이렇다.
cos(θ) = (A · B) / (|A| · |B|)A · B = 내적 = 성분끼리 곱해서 합한 값. 크기와 방향을 모두 반영한다.|A|, |B| = 각 벡터의 크기(L2 norm).실무에서는 매 검색마다 나눗셈이 비싸서, 저장할 때 모든 벡터를 L2 정규화(크기를 1로)한다.
그러면 |A|=|B|=1이 되어 cos = A·B — 코사인이 그냥 내적으로 단순화된다.
OpenSearch space_type=cosinesimil, faiss cosine이 내부적으로 이 방식(정규화 후 inner product)이다.
임베딩 모델이 의미가 비슷한 텍스트를 벡터 공간에서 방향이 비슷하게 배치하도록 학습되기 때문에, 각도(코사인)가 의미 유사도를 대변한다. HNSW는 유사도가 아니라 "거리"로 다루므로 코사인 거리 = 1 − 코사인 유사도로 변환해 쓴다(작을수록 가깝다).
같은 M/ef라도 recall·속도가 갈리는 원인은 넷이다.
| 구현체 | 특징 | 관점 |
|---|---|---|
| FAISS IndexHNSWFlat | 원본 저장(refine 정확), 순수 라이브러리 | 조합·기준용 |
| hnswlib | HNSW 원저자 계열, diverse 이웃 휴리스틱 | ann-benchmarks 순정 기준선 |
| Lucene (OpenSearch) | 초기엔 단일 레이어, 세그먼트별 그래프 | 이후 계층·SIMD·양자화로 개선 — 현행은 실측 필요 |
| Qdrant (Rust) | GC 정지 없음, filterable HNSW(추가 엣지) | 필터링 강점 주장 |
| pgvector | IVFFlat 대비 2~5배 메모리, pgvectorscale로 개선 주장 | 대규모 열세가 통설 |
| Milvus Knowhere | FAISS 통합, SIMD 자동선택, Dual-Pool 필터 | 필터링을 다른 방식으로 해결 |
특히 눈여겨볼 지점이 둘 있다.
첫째, Lucene HNSW의 역사적 함정이다. 초기 Lucene HNSW는 논문의 계층 구조를 안 따르고 단일 그래프 레이어만 썼다. 그 탓에 같은 데이터셋에서 hnswlib 대비 QPS가 약 9배 낮았다는 벤치가 있다(2021 무렵, 출처 주장). 기본 beamWidth(=ef_construction)가 16으로 너무 낮았던 것도 원인이다. 다만 이후 계층 구조 도입, SIMD(Panama Vector API), 양자화로 크게 개선됐으므로, 현행 OpenSearch를 이 옛 수치로 판단하면 안 된다.
둘째, 필터 처리 방식의 차이다. 메타데이터 필터를 걸면 그래프 탐색이 깨진다 — 필터를 탐색 전에 적용하면 그래프 연결이 끊겨 recall이 급락하고, 탐색 후에 적용하면 필터가 빡셀 때 k개를 못 채운다.
ann-benchmarks(erikbern)는 표준 데이터셋에서 recall@k·QPS·빌드시간·메모리를 재는 사실상 표준 도구다.
즉 "누가 제일 빠른가"는 라이브러리 벤치와 DB 벤치, 그리고 벤더 자사 벤치가 다 다르게 말한다. 그래서 자체 워크로드 실측이 필요하다.
ef_search, Milvus ef, Lucene beamWidth, Qdrant hnsw_ef. 같은 개념이라도 제품마다 플래그가 다르다.이 글의 수치는 공개 벤치·벤더 문서 기반의 "출처 주장"이다. 실제 제품 선택은 자체 워크로드 벤치로 재검증한 뒤 판단한다.