Workspace IndexAlgorithms › Day 78

Vector Databases and ANN Indexes (HNSW, IVF-PQ) TODO

Algorithms · Day 78 / 100 · E. Data & Storage Engines (Day 69-81)

Concept

Vector search means finding items close to a query vector in embedding space; in high dimensions, exact nearest-neighbor search effectively degrades into brute-force comparison, so approximate nearest neighbor (ANN) search is used instead. An ANN index's quality is judged on the tradeoff curve between recall (accuracy) and latency/memory, and no index escapes that curve for free. HNSW builds a hierarchical proximity graph, greedily jumping far via sparse links at the top layers and refining the search at lower layers, with a search-width parameter that trades off recall against speed. IVF-PQ first partitions the vector space into clusters so a query only scans a handful of nearby lists, then compresses vectors into per-subspace codebook indices to cut memory sharply while comparing with approximate distances. Broadly, HNSW uses more memory and gives lower latency, while IVF-PQ is more memory-efficient at large scale.

The perceived quality of RAG or similar-item recommendations is usually decided by the retriever's recall, not the generation model, so if you don't understand how index parameters affect accuracy, you'll end up looking for the cause in the wrong place.

Code & Formula

# 벡터 DB와 ANN 인덱스 — 소규모 벡터 집합에 대한 브루트포스 최근접 탐색을 베이스라인으로 구현.
# 실제 HNSW/IVF-PQ 는 이 exact 결과를 근사(recall<1.0)로 더 빠르게 흉내내는 것이 목표다.

import numpy as np

rng = np.random.default_rng(42)
DIM, N = 8, 200
vectors = rng.normal(size=(N, DIM)).astype("float32")
ids = [f"doc-{i}" for i in range(N)]

def cosine_distance(a, b):
    a_n = a / np.linalg.norm(a)
    b_n = b / (np.linalg.norm(b, axis=1, keepdims=True) + 1e-9)
    return 1.0 - b_n @ a_n

def brute_force_knn(query, k=5):
    dists = cosine_distance(query, vectors)      # 전수 비교: O(N*DIM)
    top_k = np.argsort(dists)[:k]
    return [(ids[i], float(dists[i])) for i in top_k]

query = vectors[7] + rng.normal(scale=0.05, size=DIM).astype("float32")  # doc-7 근처 질의
result = brute_force_knn(query, k=5)

print("query is a noisy copy of doc-7")
print("brute-force top-5 nearest neighbors (id, cosine distance):")
for doc_id, dist in result:
    print(f"  {doc_id}: {dist:.4f}")
print("exact search cost: O(N * DIM) per query — ANN indexes trade this for recall < 1.0")

Exercise

Build tens of thousands of embeddings, treat brute-force results as ground truth, then vary HNSW's search-width parameter and plot the recall@10 vs. query-latency tradeoff curve.

Practical Connection

Vector search on on-chain data itself is rare, but it applies directly to text assets at the service layer — deduplicating market descriptions, recommending similar markets, or searching past dispute cases.

If you study this on a given day, add a note link and a ✅ to this line in the source curriculum (docs/knowledge/dev-100-curriculum.md) and this spot will lead straight to the note body. You can also write directly on this page — but regenerating overwrites it, so it's safer to keep anything you want to save as markdown under docs/algorithms/.


한국어

벡터 DB와 ANN 인덱스(HNSW·IVF-PQ) TODO

Algorithms · Day 78 / 100 · E. 데이터·스토리지 엔진 (Day 69–81)

개념

벡터 검색은 임베딩 공간에서 질의 벡터와 가까운 항목을 찾는 문제이며, 고차원에서는 정확한 최근접 탐색이 사실상 전수 비교로 퇴화하므로 근사 최근접 탐색을 쓴다. ANN 인덱스의 품질은 정확도인 recall과 지연·메모리 사이의 트레이드오프 곡선으로 평가하며, 어떤 인덱스도 이 곡선을 벗어나 공짜로 좋아지지 않는다. HNSW는 계층적 근접 이웃 그래프를 만들어 위층의 성긴 연결로 멀리 점프하고 아래층에서 정밀 탐색하는 그리디 탐색을 수행하며, 탐색 폭 파라미터로 recall과 속도를 조절한다. IVF-PQ는 먼저 벡터 공간을 클러스터로 나눠 질의와 가까운 몇 개 리스트만 조사하고, 벡터를 부분 공간별 코드북 인덱스로 압축해 메모리를 크게 줄이면서 근사 거리로 비교한다. 대체로 HNSW는 메모리를 더 쓰고 지연이 낮으며, IVF-PQ는 대규모 데이터에서 메모리 효율이 좋다.

RAG나 유사 항목 추천의 체감 품질은 대개 생성 모델이 아니라 리트리버의 recall에서 갈리므로, 인덱스 파라미터가 정확도에 미치는 영향을 모르면 원인을 엉뚱한 데서 찾게 된다.

코드 · 수식

# 벡터 DB와 ANN 인덱스 — 소규모 벡터 집합에 대한 브루트포스 최근접 탐색을 베이스라인으로 구현.
# 실제 HNSW/IVF-PQ 는 이 exact 결과를 근사(recall<1.0)로 더 빠르게 흉내내는 것이 목표다.

import numpy as np

rng = np.random.default_rng(42)
DIM, N = 8, 200
vectors = rng.normal(size=(N, DIM)).astype("float32")
ids = [f"doc-{i}" for i in range(N)]

def cosine_distance(a, b):
    a_n = a / np.linalg.norm(a)
    b_n = b / (np.linalg.norm(b, axis=1, keepdims=True) + 1e-9)
    return 1.0 - b_n @ a_n

def brute_force_knn(query, k=5):
    dists = cosine_distance(query, vectors)      # 전수 비교: O(N*DIM)
    top_k = np.argsort(dists)[:k]
    return [(ids[i], float(dists[i])) for i in top_k]

query = vectors[7] + rng.normal(scale=0.05, size=DIM).astype("float32")  # doc-7 근처 질의
result = brute_force_knn(query, k=5)

print("query is a noisy copy of doc-7")
print("brute-force top-5 nearest neighbors (id, cosine distance):")
for doc_id, dist in result:
    print(f"  {doc_id}: {dist:.4f}")
print("exact search cost: O(N * DIM) per query — ANN indexes trade this for recall < 1.0")

연습

수만 건 규모의 임베딩을 만들어 brute force 결과를 정답으로 두고, HNSW의 탐색 폭 파라미터를 바꿔 가며 recall@10과 질의 지연을 측정해 트레이드오프 곡선을 그려 보라.

실무 · Verex 연결

온체인 데이터 자체를 벡터 검색할 일은 드물지만, 마켓 설명문의 중복 탐지나 유사 마켓 추천, 분쟁 사례 검색처럼 서비스 계층의 텍스트 자산에는 그대로 적용된다.

공부한 날 원본 커리큘럼(docs/knowledge/dev-100-curriculum.md)의 이 줄에 노트 링크와 ✅ 를 붙이면, 이 자리는 노트 본문으로 바로 이어집니다. 노트 없이 이 페이지에 바로 적어도 됩니다 — 다만 다시 생성하면 덮어쓰이므로, 남길 글은 docs/algorithms/ 의 마크다운으로 쓰는 편이 안전합니다.

← 77. 스트리밍 처리 의미론79. 캐시 일관성·무효화·스탬피드 방지 →