Tipe vector menyimpan embedding sebagai kolom tabel PostgreSQL, sedangkan indeks HNSW dan IVFFlat menentukan kecepatan pencariannya. Topik ini membahas deklarasi kolom vector beserta batas dimensinya, pemilihan operator class pada kedua jenis indeks, parameter pembangunannya, dan urutan kerja yang membuat pembangunan indeks lebih cepat.
Deklarasi kolom bertipe vector#
Ekstensi vector diaktifkan terlebih dahulu pada basis data yang akan memuat kolom vektor, karena ekstensi Postgres berlaku per basis data. Setelah ekstensi aktif, kolom bertipe vector dideklarasikan dengan jumlah dimensi yang tetap. Contoh berikut membuat tabel items sesuai dokumentasi pgvector, lalu mengisi dua baris dengan literal vektor:
-- tidak-dijalankan
CREATE TABLE items (
id bigserial PRIMARY KEY,
embedding vector(3)
);
INSERT INTO items (embedding) VALUES ('[3,1,2]'), ('[4,5,6]');Jumlah dimensi ditulis langsung pada deklarasi tipe dan bersifat tetap. Penyisipan vektor yang panjangnya berbeda dari deklarasi gagal dengan pesan error, sehingga kesalahan dimensi tertangkap pada saat penyisipan. Sebuah kolom vector menerima hingga 16.000 dimensi, sedangkan indeks pada tipe vector hanya mendukung hingga 2000 dimensi. Untuk rentang di antara keduanya, pgvector menyediakan tipe halfvec yang dapat diindeks hingga 4000 dimensi, tipe sparsevec untuk vektor jarang dengan hingga 1000 elemen tak nol, dan tipe bit untuk hingga 64.000 dimensi.
Setiap komponen vektor disimpan sebagai float32 dengan 4 byte per dimensi, sehingga kolom vector(3) pada contoh menempati 12 byte per baris di luar overhead barisnya. Nilai dimensi yang dideklarasikan sebaiknya mengikuti keluaran model embedding yang dipilih, misalnya 384 atau 768, karena perubahan model menuntut perubahan skema. Dimensi 3 pada contoh dipilih semata agar literalnya pendek dan mudah dibaca.
Operator class pada indeks HNSW#
Indeks pada kolom vektor membutuhkan pilihan operator class, yaitu kelas operator yang menentukan metrik jarak yang dikelola indeks. pgvector menyediakan vector_l2_ops untuk jarak L2, vector_ip_ops untuk inner product, dan vector_cosine_ops untuk jarak cosine. Pilihan ini harus cocok dengan operator yang digunakan pada query, karena planner hanya memanfaatkan indeks apabila operatornya berada dalam operator class yang sama. Perintah berikut membuat indeks HNSW dengan operator class cosine beserta dua parameter pembangunannya:
-- tidak-dijalankan
CREATE INDEX ON items USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);Parameter m menentukan jumlah tautan per titik pada graf HNSW, dan nilai 16 merupakan nilai umum yang juga digunakan contoh dokumentasinya. Parameter ef_construction mengatur ukuran kumpulan kandidat saat graf dibangun, dengan nilai umum 64. Nilai yang lebih besar untuk keduanya memperbaiki kualitas graf dan dengan demikian kualitas pencarian, tetapi memperpanjang waktu pembangunan dan memperbesar memori indeks.
Indeks IVFFlat dan pembagian klasternya#
Indeks IVFFlat membagi seluruh vektor ke dalam sejumlah klaster, dan jumlah klaster itu ditetapkan melalui parameter lists saat indeks dibuat:
-- tidak-dijalankan
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);Pada saat pencarian, query hanya menyapu klaster-klaster yang centroidnya paling dekat dari vektor query. Jumlah klaster yang disapu diatur per sesi melalui ivfflat.probes dengan nilai default 1, dan contoh nilai yang lazim berupa SET ivfflat.probes = 10;. Dengan lists = 100 pada data yang tersebar merata, satu probe menyapu kira-kira satu persen baris. Menambah probes meningkatkan peluang menemukan tetangga terbaik, sementara biaya pemindaian naik mengikuti.
Dokumentasi pgvector menyarankan jumlah lists sekitar jumlah baris dibagi 1000 untuk data hingga 1 juta baris, dan sekitar akar kuadrat jumlah baris untuk data yang lebih besar. Pembangunan IVFFlat menuntut keberadaan data karena klaster dibentuk dari data yang ada saat indeks dibangun, sehingga indeks sebaiknya dibuat setelah tabel berisi baris yang representatif. Indeks yang dibangun pada tabel yang masih kosong menghasilkan pembagian klaster yang buruk dan perlu dibangun ulang setelah data dimuat.
Urutan memuat data dan membangun indeks#
Aturan memuat dahulu juga berlaku demi kecepatan. Memuat data terlebih dahulu, lalu membangun indeks sekali di akhir, lebih cepat daripada membuat indeks sebelum memuat, karena setiap penyisipan ke tabel yang sudah terindeks ikut memperbarui indeksnya. Contoh urutan kerja yang lazim berbentuk berikut:
-- tidak-dijalankan
-- muat data terlebih dahulu
INSERT INTO items (embedding)
VALUES ('[3,1,2]'), ('[4,5,6]'), ('[5,4,3]');
-- bangun indeks setelah tabel berisi data representatif
CREATE INDEX ON items USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);Pada data yang besar, pembangunan sekali di akhir juga menghasilkan indeks yang lebih tepat karena strukturnya disusun dari data yang lengkap. Kebiasaan yang sama berlaku saat memuat ulang data dalam jumlah besar, yaitu menunda pembangunan indeks hingga pemuatan selesai. Pilihan antara HNSW dan IVFFlat menyangkut kompromi yang jelas. HNSW biasanya memberi kualitas pencarian lebih baik daripada IVFFlat pada kecepatan yang setara, tetapi lebih lama dibangun dan memerlukan memori lebih besar untuk menyimpan tautan grafnya. IVFFlat lebih cepat dibangun dan biaya pencariannya mudah diatur melalui probes, dengan kualitas yang bergantung pada pembagian klasternya. Kedua indeks sama-sama aproksimatif, sehingga kualitas keduanya diukur terhadap jawaban eksak pada data masing-masing.
Kesalahan umum#
- Membangun indeks IVFFlat saat tabel masih kosong membuat klaster terbentuk dari data yang hampir tidak ada. Pembagian yang dihasilkan tidak mewakili data sebenarnya, sehingga recall turun tanpa pesan apa pun. Perbaikannya, muat data terlebih dahulu, atau bangun ulang indeks setelah data representatif tersedia.
- Memasangkan operator query dengan operator class yang berbeda membuat indeks diabaikan tanpa peringatan. Query dengan
<=>hanya memperoleh manfaat dari indeks denganvector_cosine_ops, dan begitu pula pasangan lainnya. Perbaikannya, cocokkan keduanya, lalu periksa dengan EXPLAIN bahwa indeks benar-benar digunakan. - Mendeklarasikan dimensi yang tidak sesuai model embedding memicu error pada saat data nyata disisipkan. Kolom vector(3) pada contoh hanya untuk ilustrasi, sedangkan model yang umum menghasilkan 384 hingga 1536 dimensi. Perbaikannya, tetapkan dimensi sesuai model yang dipilih sebelum data dimuat.
- Mengubah parameter
matauef_constructionpada indeks yang sudah berjalan diharapkan berlaku seketika, padahal kedua parameter menentukan bentuk indeks saat dibangun dan hanya berlaku setelah pembangunan ulang. Perbaikannya, bangun ulang indeks dengan nilai baru pada jendela pemeliharaan, lalu ukur ulang kualitas pencariannya.
Ringkasan#
- Kolom bertipe vector menyimpan vektor berdimensi tetap hingga 16.000 dimensi dengan elemen float32, sedangkan indeksnya mendukung hingga 2000 dimensi.
- Operator class (
vector_l2_ops,vector_ip_ops,vector_cosine_ops) menentukan metrik indeks dan harus cocok dengan operator pada query. - Indeks HNSW diatur dengan
mdanef_constructionsaat dibangun, sedangkan IVFFlat diatur denganlistssaat dibangun danprobessaat mencari. - IVFFlat membutuhkan data untuk membentuk klaster, sehingga memuat data terlebih dahulu sebelum membangun indeks lebih cepat dan lebih tepat.
Referensi#
- pgvector — GitHub — Dokumentasi pembuatan indeks HNSW dan IVFFlat, operator class, batas dimensi, dan saran jumlah lists.
- Dokumentasi PostgreSQL — PostgreSQL — Rujukan pernyataan CREATE INDEX dan perilaku indeks Postgres.