Implementasi similarity search skala besar menggunakan ekstensi pgvector sering kali berujung pada degradasi performa drastis saat ukuran dataset melampaui kapasitas RAM. Pola yang umum terjadi: latensi query melonjak dari 5–10 milidetik menjadi hitungan detik, disk IOPS mencapai titik jenuh, dan utilisasi CPU meningkat akibat I/O wait. Gejala ini merupakan indikasi memory thrashing.
Akar Masalah: Mengapa HNSW Memicu Memory Thrashing?
Indeks Hierarchical Navigable Small World (HNSW) bekerja dengan memetakan vektor ke dalam struktur graf multi-layer berkerapatan tinggi. Setiap simpul (vektor) menyimpan daftar pointer dua arah menuju simpul-simpul tetangganya. Saat kueri pencarian vektor dijalankan, algoritme melakukan graph traversal berupa lompatan acak antar node melintasi layer dari atas ke bawah untuk menemukan nearest neighbors.
Karakteristik akses data pada HNSW bersifat non-sequential random access. Formula estimasi konsumsi memori untuk indeks HNSW adalah:
Ukuran Indeks ≈ Jumlah_Row × ((Dimensi × 4 byte) + (m × 8 byte overhead pointer) + metadata)Sebagai contoh, 1 juta vektor dengan dimensi 1536 (standar OpenAI text-embedding-3-small) dengan parameter koneksi m = 16 membutuhkan ruang indeks lebih dari 6–8 GB, di luar ukuran tabel aslinya. Jika ukuran total working set graf HNSW melampaui alokasi shared_buffers dan cache sistem operasi (OS page cache), engine PostgreSQL terpaksa membaca halaman indeks langsung dari storage (disk read).
Karena traversal graf memerlukan pembacaan simpul secara rekursif acak, sequential prefetching milik kernel tidak dapat bekerja efektif. Halaman memori terus-menerus digusur (evicted) dan dimuat ulang dari disk secara simultan. Inilah yang menyebabkan lonjakan tajam pada metrik Buffer Pool Evictions dan disk IOPS.
Trade-off Arsitektural: HNSW vs IVFFlat
Sebelum melakukan tuning lebih jauh, tentukan apakah HNSW merupakan indeks yang tepat untuk beban kerja Anda dibandingkan alternatifnya, IVFFlat.
- HNSW: Memberikan recall tinggi (>98%) secara konsisten tanpa memerlukan warm-up dataset besar. Query latency stabil pada skala pembacaan tinggi. Kelemahannya terletak pada konsumsi RAM yang tinggi (hingga 5–10x lebih besar dari IVFFlat) dan durasi pembangunan indeks yang sangat lama.
- IVFFlat: Mengelompokkan vektor ke dalam centroid menggunakan algoritma k-means. Jejak memori jauh lebih kecil dan proses build jauh lebih cepat. Kelemahannya: recall menurun signifikan jika data baru mengalami distribution shift, serta membutuhkan fase training awal via parameter
lists.
Gunakan HNSW jika throughput query rendah-hingga-sedang membutuhkan recall presisi tinggi secara real-time. Gunakan IVFFlat jika batas toleransi recall lebih longgar dan ketersediaan memori sangat terbatas.
Parameter Tuning: Build Time vs Runtime
Optimasi HNSW dibagi menjadi dua fase: parameter struktur saat indeks dibuat (DDL) dan parameter penelusuran saat query dieksekusi (runtime DML).
1. Pembangunan Indeks (Build Time)
Dua parameter utama yang menentukan bentuk graf adalah m dan ef_construction:
m: Jumlah koneksi maksimal per node. Nilai default adalah 16. Menaikkan nilai ini (misal ke 24 atau 32) meningkatkan recall dan stabilitas graf pada vektor berdimensi tinggi, tetapi menambah konsumsi RAM indeks dan durasi build.ef_construction: Ukuran candidate list selama penelusuran saat node baru disisipkan. Nilai default adalah 64. Nilai 128 hingga 200 meningkatkan kualitas konektivitas graf tanpa mengubah ukuran akhir file indeks di disk.
Sebelum menjalankan CREATE INDEX, tingkatkan alokasi memori kerja proses untuk mencegah spill ke temporary disk:
-- Konfigurasi sesi untuk build index cepat
SET maintenance_work_mem = '8GB';
SET max_parallel_maintenance_workers = 4;
CREATE INDEX CONCURRENTLY idx_items_embedding_hnsw
ON items
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);2. Tuning Pencarian Sesi (Runtime)
Akurasi dan latensi kueri dikontrol langsung melalui parameter dinamis hnsw.ef_search. Parameter ini menentukan kedalaman eksplorasi candidate list selama proses traversal pencarian.
-- Default bernilai 40
SET hnsw.ef_search = 40;
-- Turunkan untuk kueri berlatensi sangat rendah (trade-off: recall turun)
SET hnsw.ef_search = 20;
-- Naikkan untuk kueri presisi tinggi
SET hnsw.ef_search = 100;Jebakan Post-Filtering pada Hybrid Search
Salah satu sumber utama bottleneck adalah menggabungkan filter relasional dengan vector similarity search. Pertimbangkan kueri berikut:
SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> '[0.012, -0.045, ...]'::vector
LIMIT 10;Sebelum pgvector versi 0.7.0, PostgreSQL sering menggunakan pendekatan post-filtering: traversal HNSW mengambil top-K kandidat global (misalnya 40 node terdekat sesuai ef_search), lalu mengeksekusi filter tenant_id = 42 pada baris-baris tersebut. Jika data untuk tenant tersebut hanya merepresentasikan 1% dari total data, seluruh kandidat gugur, menghasilkan result set kosong atau memicu sequential scan yang lambat.
Mendiagnosis Lewat EXPLAIN ANALYZE
Periksa rencana kueri dengan menyertakan metrik buffer:
EXPLAIN (ANALYZE, BUFFERS)
SELECT id, content
FROM documents
WHERE tenant_id = 42
ORDER BY embedding <=> '[0.012, -0.045, ...]'::vector
LIMIT 10;Perhatikan indikator berikut pada output:
Buffers: shared hit=... read=...: Nilaireadyang tinggi secara kontinu mengindikasikan halaman indeks dibaca dari storage karena tidak muat dishared_buffers(thrashing).Filter: (tenant_id = 42)denganRows Removed by Filterbernilai besar: Menunjukkan indeks HNSW memindai banyak vektor yang tidak relevan sebelum filter relasional diaplikasikan.
Strategi Partisi: Menjaga Working Set di RAM
Untuk mengeliminasi overhead traversal pada data yang tidak relevan serta menjaga ukuran graf HNSW tetap berada di dalam memori, terapkan Declarative Table Partitioning. Dengan memecah tabel berdasarkan dimensi relasional (seperti tenant_id, region, atau range waktu), indeks HNSW dibangun secara independen per partisi.
-- 1. Buat partitioned table
CREATE TABLE documents (
id bigint GENERATED ALWAYS AS IDENTITY,
tenant_id int NOT NULL,
content text NOT NULL,
embedding vector(1536) NOT NULL,
PRIMARY KEY (tenant_id, id)
) PARTITION BY LIST (tenant_id);
-- 2. Buat partisi spesifik
CREATE TABLE documents_tenant_1 PARTITION OF documents
FOR VALUES IN (1);
CREATE TABLE documents_tenant_2 PARTITION OF documents
FOR VALUES IN (2);
-- 3. Bangun indeks HNSW lokal pada masing-masing partisi
CREATE INDEX idx_docs_t1_hnsw ON documents_tenant_1
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);
CREATE INDEX idx_docs_t2_hnsw ON documents_tenant_2
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 128);Saat kueri menyertakan klausa WHERE tenant_id = 1, query optimizer menggunakan mekanisme partition pruning. Database hanya mengakses indeks graf milik partisi yang bersangkutan. Indeks yang lebih kecil menjamin traversal hanya memuat data aktif, mempertahankan utilisasi cache RAM mendekati 100%, serta sepenuhnya meniadakan degradasi IOPS akibat memory thrashing.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!