Pencarian kemiripan pada pgvector ditulis sebagai pernyataan SELECT biasa yang mengurutkan baris menurut jarak vektor lalu mengambil k terbaik. Topik ini membahas operator jarak yang tersedia, interpretasi skor jarak cosine, penulisan query terparameter dari kode aplikasi, dan pemeriksaan penggunaan indeks dengan EXPLAIN.
Operator jarak dan fungsi padanannya#
pgvector menyediakan enam operator jarak yang dapat digunakan di dalam pernyataan SQL, ditambah fungsi dengan nama lengkap yang menghitung ukuran yang sama. Operator beroperasi pada ekspresi vektor, misalnya kolom embedding terhadap vektor query, dan menghasilkan bilangan pecahan yang maknanya bergantung pada operatornya. Tabel berikut merangkum keenam operator beserta fungsi padanannya.
| Operator | Ukuran yang dihasilkan | Fungsi padanan |
|---|---|---|
<=> | jarak cosine | cosine_distance() |
<-> | jarak L2 (Euclidean) | l2_distance() |
<#> | inner product negatif | inner_product() |
<+> | jarak L1 | l1_distance() |
<~> | jarak Hamming (tipe bit) | tidak ada |
<%> | jarak Jaccard (tipe bit) | tidak ada |
Skor <=> disebut jarak cosine, yaitu satu dikurangi cosine similarity. Hubungan keduanya ditulis sebagai berikut, dengan vektor query dan vektor yang dibandingkan:
Jarak cosine bernilai 0 apabila dua vektor searah sempurna, mendekati 1 apabila keduanya saling tegak lurus, dan mendekati 2 apabila keduanya berlawanan arah. Nilai yang lebih kecil berarti lebih mirip, sehingga pengurutannya dilakukan menaik (ASC). Apabila aplikasi memerlukan skor yang lebih besar berarti lebih mirip, konversinya berbentuk satu dikurangi jarak.
Query kNN dengan ORDER BY dan LIMIT#
Bentuk pencarian terdekat (kNN) yang lazim mengurutkan seluruh baris menurut jaraknya lalu mengambil k terbaik dengan LIMIT:
-- tidak-dijalankan
SELECT id, embedding <=> '[3,1,2]' AS jarak
FROM items
ORDER BY embedding <=> '[3,1,2]'
LIMIT 5;LIMIT menentukan nilai k, dan nilai yang lazim berada pada rentang 5 hingga 20 sesuai kebutuhan antarmuka yang menampilkannya. Semakin besar k, semakin banyak pekerjaan yang dilakukan indeks atau pemindaian pada setiap query. Vektor query ditulis sebagai literal '[3,1,2]' dan dikonversi ke tipe vector secara otomatis. Tanpa indeks, pernyataan ini menjalankan pemindaian eksak yang menghitung jarak untuk setiap baris tabel, sehingga hasilnya benar tetapi berbiaya sebanding dengan jumlah baris. Dengan indeks HNSW atau IVFFlat yang operator class-nya cocok, rencana eksekusi berubah menjadi pencarian aproksimatif yang jauh lebih murah pada data besar. Konversi skor kemiripan dilakukan langsung di dalam daftar kolom:
-- tidak-dijalankan
SELECT id, 1 - (embedding <=> '[3,1,2]') AS skor
FROM items
ORDER BY embedding <=> '[3,1,2]'
LIMIT 5;Query terparameter dari kode aplikasi#
Query yang dikirim dari aplikasi ditulis terparameter, yaitu nilai dikirim terpisah dari teks SQL melalui placeholder driver. Pada psycopg, placeholder berbentuk %s, sehingga teks SQL dan daftar parameternya diserahkan sebagai dua bagian terpisah ke cursor. Contoh berikut memperlihatkan bentuk pemanggilannya:
# tidak-dijalankan
# psycopg tidak tersedia di sandbox, contoh ini memperlihatkan bentuk pemanggilannya
sql = "SELECT id FROM items WHERE kategori_id = %s ORDER BY embedding <=> %s LIMIT %s"
cur.execute(sql, (2, "[3,1,2]", 5))Nilai vektor query dikirim sebagai teks '[3,1,2]' dan dikonversi ke tipe vector oleh Postgres, sedangkan nilai filter dan LIMIT ikut terparameter. Pembagian ini menutup celah SQL injection karena masukan pengguna tidak pernah disambungkan ke dalam teks SQL. Kebiasaan yang sama diterapkan pada pernyataan lain yang menyertakan nilai masukan, sehingga seluruh lapisan akses data mengikuti satu pola yang sama. Pola itu juga berlaku pada driver lain dengan bentuk placeholder yang berbeda, misalnya $1 dan $2 pada klien yang mengikuti gaya protokol PostgreSQL.
Memeriksa penggunaan indeks dengan EXPLAIN#
Keyakinan bahwa indeks digunakan perlu diverifikasi dengan EXPLAIN ANALYZE, yaitu perintah Postgres yang menampilkan rencana eksekusi sekaligus menjalankan pernyataannya untuk mencatat waktu dan jumlah baris nyata. Contoh pemanggilannya berbentuk berikut:
-- tidak-dijalankan
EXPLAIN ANALYZE
SELECT id FROM items
ORDER BY embedding <=> '[3,1,2]'
LIMIT 5;Limit (cost=0.12..4.23 rows=5 width=16) (actual time=0.290..0.362 rows=5 loops=1)
-> Index Scan using idx_items_hnsw on items (actual time=0.288..0.357 rows=5 loops=1)
Order By: (embedding <=> '[3,1,2]'::vector)
Planning Time: 0.116 ms
Execution Time: 0.394 msBentuk dan angka keluaran EXPLAIN berbeda antar versi Postgres, ukuran data, dan kehadiran indeks, sehingga bagian yang digunakan hanya bentuk nodenya. Node Index Scan using idx_items_hnsw menunjukkan indeks HNSW digunakan untuk menjawab pengurutan jaraknya. Apabila node yang muncul berupa Seq Scan, seluruh tabel dipindai, dan penyebab yang lazim berupa indeks yang belum dibuat, operator yang tidak cocok dengan operator class indeks, atau data yang masih kecil sehingga pemindaian berurutan dinilai planner lebih murah.
Perbandingan sebelum dan sesudah indeks dibuat memberi gambaran biaya yang nyata pada data masing-masing. Keluaran ANALYZE mencatat waktu eksekusi nyata per node, sehingga perbedaan latensi antara pemindaian eksak dan pencarian terindeks terlihat langsung. Pengukuran ini sebaiknya diulang pada ukuran data yang menyerupai produksi, karena planner dapat mengubah pilihannya mengikuti statistik tabel yang dimilikinya.
Kesalahan umum#
- Mengurutkan jarak dengan
DESCmengembalikan baris yang paling jauh terlebih dahulu, bukan yang paling mirip. Nilai jarak yang kecil berarti dekat, sehingga pencarian terdekat selalu diurutkan menaik. Perbaikannya, gunakan urutan menaik dan batasi dengan LIMIT. - Menafsirkan kolom jarak cosine sebagai skor kemiripan membuat laporan aplikasi terbalik maknanya, karena nilai 0 berarti paling mirip pada jarak, sedangkan nilai 1 berarti paling mirip pada skor. Perbaikannya, tetapkan bentuk yang digunakan aplikasi, lalu konversi dengan satu dikurangi jarak hanya di titik yang menampilkan skor.
- Menyusun SQL dengan penyambungan string membuat query rentan SQL injection dan mempersulit penggunaan rencana eksekusi. Perbaikannya, gunakan placeholder driver untuk seluruh nilai masukan.
- Membaca keluaran EXPLAIN tanpa opsi ANALYZE membuat perkiraan biaya disangka sebagai waktu nyata. Nilai cost pada rencana merupakan perkiraan planner, sedangkan waktu eksekusi nyata hanya muncul dengan ANALYZE. Perbaikannya, gunakan EXPLAIN ANALYZE untuk membandingkan latensi.
Ringkasan#
- Operator jarak pgvector mencakup
<=>cosine,<->L2,<#>inner product negatif,<+>L1,<~>Hamming, dan<%>Jaccard, dengan fungsi padanan seperticosine_distance()danl2_distance(). - Pencarian kNN berbentuk ORDER BY jarak diikuti LIMIT k, dan jarak cosine bernilai satu dikurangi cosine similarity sehingga urutannya menaik.
- Query dari aplikasi ditulis terparameter melalui placeholder driver agar nilai tidak pernah disambungkan ke teks SQL.
- EXPLAIN ANALYZE memperlihatkan node rencana eksekusi, dan kehadiran
Index Scanmenandakan indeks benar-benar digunakan.
Referensi#
- pgvector — GitHub — Daftar operator jarak, fungsi padanannya, dan contoh query pencarian terdekat pada dokumentasi resminya.
- Dokumentasi PostgreSQL — PostgreSQL — Rujukan perintah EXPLAIN ANALYZE dan pembacaan rencana eksekusi Postgres.