πŸ—„οΈ Vector Database Internals & Optimization β€” HNSW, IVF, PQ, dan Performa Search di vault-rag

Vector database adalah engine yang membuat semantic search mungkin dalam milidetik β€” bukan dengan membandingkan query ke semua dokumen (brute force), tapi dengan struktur index yang cerdas. Dokumen ini membedah arsitektur index vector: HNSW (Hierarchical Navigable Small World), IVF (Inverted File Index), Product Quantization (PQ), scalar quantization, dan bagaimana sqlite-vec (yang dipake vault-rag) bekerja di bawah hood. Plus: strategi tuning untuk performa search vs akurasi.

Hubungan ke Vault

vault-rag menggunakan sqlite-vec dengan cosine search (brute force β€” karena ukuran data masih kecil). Catatan ini membahas scaling: kapan perlu HNSW, trade-off akurasi vs latency, dan optimasi index. Terkait dengan embedding-model-selection-finetuning (dimensi β†’ performa index), hybrid-search-vector-keyword (dense + sparse === dua index berbeda), dan index_vault.py (sqlite-vec integration).


Daftar Isi


1.1 Kompleksitas

MethodSearch ComplexityMemoryAkurasiCocok untuk
Brute Force (flat)O(nΓ—d)Rendah100%Dataset < 10K vektor
HNSWO(log n)Tinggi (1.5-2x)~99% recall@10Dataset > 10K, butuh latency rendah
IVFO(√n)Rendah~95% recallDataset besar, akurasi bukan prioritas
IVF+PQO(√n)Sangat rendah (0.2x)~90% recallDataset sangat besar (>1M), memory terbatas

Posisi vault-rag: Saat ini <10K chunks β†’ brute force sudah cukup. Kalo vault tumbuh >50K chunks, baru perlu pindah ke HNSW.

1.2 Trade-off

Akurasi (%)
   100β”‚  ● Brute Force
       β”‚
    99 β”‚     ● HNSW
       β”‚
    95 β”‚           ● IVF
       β”‚
    90 β”‚                ● IVF+PQ
       β”‚
       └──────────────────────────→ Latency (ms)
         1ms    10ms    100ms

2. HNSW (Hierarchical Navigable Small World)

2.1 Cara Kerja

HNSW membangun multi-layer graph. Layer paling atas = β€œjalan tol” (few nodes, long jumps). Layer paling bawah = β€œjalan lokal” (all nodes, nearest neighbors).

Layer 3 (2 nodes)     β—‹ ←─────────────────── β—‹
                       β”‚                       β”‚
Layer 2 (8 nodes)    ○─○─○  ←───────────  ○─○─○
                       β”‚  β”‚                  β”‚  β”‚
Layer 1 (32 nodes)   ○─○─○─○─○─○─○─○  ○─○─○─○─○─○
                       β”‚  β”‚  β”‚  β”‚  β”‚  β”‚  β”‚  β”‚  β”‚  β”‚
Layer 0 (all)        ○─○─○─○─○─○─○─○─○─○─○─○─○─○─○

Search: Mulai dari layer teratas β†’ cari node terdekat β†’ turun layer β†’ refine β†’ sampe layer 0 dapet nearest neighbor sejati.

2.2 Parameter

ParameterDefaultRendahTinggi
M (max connections)16Index kecil, recall turunIndex besar, recall naik, memory naik
ef_construction200Index building cepatIndex building lambat, recall naik
ef_search50 (per query)Search cepat, recall turunSearch lambat, recall naik

2.3 Implementasi

# Contoh: HNSW dengan FAISS
import faiss
import numpy as np
 
d = 256  # dimensi embedding
index = faiss.IndexHNSWFlat(d, M=32)  # M = 32 connections per node
index.hnsw.efConstruction = 200       # build quality
index.add(embeddings)                 # add vectors
 
# Search
index.hnsw.efSearch = 64              # search quality
distances, indices = index.search(query_vector, k=10)
 
# Simpan & load
faiss.write_index(index, "hnsw.index")
index = faiss.read_index("hnsw.index")

3. IVF (Inverted File Index)

3.1 Cara Kerja

IVF membagi ruang vektor menjadi N cluster (via k-means). Search: cari cluster terdekat β†’ search hanya di dalam cluster itu.

Ruang Vektor:
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Cluster  β”‚ Cluster  β”‚ Cluster  β”‚
β”‚ 0        β”‚ 1        β”‚ 2        β”‚
β”‚ β—‹ β—‹ β—‹    β”‚ β—‹ β—‹      β”‚ β—‹ β—‹ β—‹ β—‹  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Cluster  β”‚ ● Query  β”‚ Cluster  β”‚
β”‚ 3        β”‚          β”‚ 4        β”‚
β”‚ β—‹ β—‹      β”‚          β”‚ β—‹ β—‹      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Cluster  β”‚ Cluster  β”‚ Cluster  β”‚
β”‚ 5        β”‚ 6        β”‚ 7        β”‚
β”‚ β—‹ β—‹ β—‹ β—‹  β”‚ β—‹ β—‹ β—‹    β”‚ β—‹ β—‹      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β†’ Cari cluster terdekat (cluster 3 & 4)
β†’ Search hanya di cluster 3 & 4
β†’ Skip cluster 0, 1, 2, 5, 6, 7

3.2 Parameter

ParameterDefaultEfek
nlist (jumlah cluster)100Makin banyak β†’ search cepat, recall turun
nprobe (cluster dicari)1Makin banyak β†’ recall naik, search lambat

4. Quantization

Quantization = mengurangi presisi angka untuk menghemat memory & mempercepat search.

4.1 Scalar Quantization (SQ)

# Float32 β†’ int8 (4x lebih kecil, sedikit akurasi turun)
# SQ: setiap dimensi di-kuantisasi ke 8-bit integer
# Sebelum: 1536 dimensi Γ— 4 byte = 6144 byte per vektor
# Sesudah: 1536 Γ— 1 byte = 1536 byte per vektor (4x lebih kecil)
 
faiss.IndexScalarQuantizer(d, qtype=faiss.ScalarQuantizer.QT_8bit)

4.2 Product Quantization (PQ)

PQ memecah vektor jadi sub-vektor dan mengkuantisasi MASING-MASING:

Vektor 256d: [0.23, -0.45, 0.12, ..., 0.89, -0.33, 0.67]
               β”œβ”€β”€β”€β”€β”€β”€β”€ M=4 sub-vektor ─────────
               [0.23, ..., 0.12]     [0.89, ..., 0.67]
                     ↓                        ↓
              Code: 42                   Code: 127
                     ↓                        ↓
            PQ code: [42, 127, 8, 95]  (hanya 4 byte!)
PQ ConfigBitrateCompressionRecall @10
M=8, nbits=864 bit/dim4x95%
M=16, nbits=832 bit8x92%
M=32, nbits=816 bit16x87%

4.3 Binary Quantization

Cohere & Jina support binary embedding: vektor jadi bit (0/1). Satu vektor 1024d = 128 bytes. Search pake Hamming distance (XOR + popcount) β€” sangat cepat, bisa 100x lebih cepat dari cosine.


5. sqlite-vec Internals

sqlite-vec adalah yang dipake vault-rag. Ini dia cara kerjanya:

5.1 Virtual Table vec0

-- sqlite-vec membuat virtual table dengan kolom:
-- rowid (integer) β€” implicit
-- embedding (float[N]) β€” vector column
 
CREATE VIRTUAL TABLE vec_chunks USING vec0(
    embedding float[256]  -- 256 = dimensi
);

Search dengan MATCH:

SELECT rowid, distance FROM vec_chunks
WHERE embedding MATCH ?
ORDER BY distance LIMIT 10;

5.2 Tentang Index

sqlite-vec tidak membangun HNSW/IVF. Search dilakukan dengan brute force β€” membandingkan query dengan semua vektor. Ini OK untuk dataset < 100K vektor.

Kinerja:

VektorLatency (256d, brute force)
1K~1ms
10K~5ms
100K~50ms
1M~500ms

5.3 Scaling

Kalo vault-rag > 100K chunks (vault tumbuh), strategi:

  1. sqlite-vec + partial index β€” filter dulu pake metadata (tahun, kategori), baru vector search
  2. FAISS HNSW β€” export embedding ke FAISS β†’ HNSW search β†’ ambil rowid β†’ JOIN ke SQLite
  3. LanceDB β€” vector DB native yang pake SQLite + HNSW built-in

6. Kapan Pake Apa

Dataset SizeRecommendedAlasan
< 10K vectorssqlite-vec (brute force)Sederhana, zero overhead. Ini vault-rag sekarang
10K - 100KHNSW via FAISSLatency <10ms, akurasi 99%
100K - 1MIVF+PQ via FAISSMemory 4-8x lebih kecil dari HNSW
> 1MHNSW + PQBest trade-off latency/akurasi/memory

Koneksi ke Vault


References

  1. FAISS. Documentation. https://faiss.ai/
  2. HNSW Paper. Y. Malkov, D. Yashunin (2016). https://arxiv.org/abs/1603.09320
  3. sqlite-vec. https://github.com/asg017/sqlite-vec
  4. Product Quantization. H. Jegou et al. (2011). https://arxiv.org/abs/1007.1022
  5. LanceDB. https://lancedb.github.io/lancedb/
  6. Cohere Binary Embeddings. https://txt.cohere.com/introducing-binary-embeddings/

Bottom Line

Vector database adalah tentang trade-off. vault-rag dengan sqlite-vec + brute force sudah cukup untuk dataset sekarang. Monitoring: kalo query latency > 100ms, saatnya pindah ke HNSW. Formula: (< 50K vektor) β†’ sqlite-vec. (> 50K) β†’ FAISS HNSW. (> 1M) β†’ IVF+PQ. Jangan optimasi sebelum waktunya β€” YAGNI.