Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Ekstensi ini pgvector menambahkan pencarian kesamaan vektor sumber terbuka ke server fleksibel Azure Database for PostgreSQL.
Artikel ini mengeksplorasi batasan dan tradeoff pgvector dan menunjukkan cara menggunakan pengaturan partisi, pengindeksan, dan pencarian untuk meningkatkan performa.
Untuk informasi selengkapnya tentang ekstensi itu sendiri, lihat dasar-dasar pgvector. Anda mungkin juga ingin merujuk ke README resmi proyek.
Performa
Anda harus selalu mulai dengan menyelidiki rencana kueri. Jika kueri Anda berakhir dengan cukup cepat, jalankan EXPLAIN (ANALYZE,VERBOSE, BUFFERS).
EXPLAIN (ANALYZE, VERBOSE, BUFFERS) SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
Untuk kueri yang membutuhkan waktu terlalu lama untuk dijalankan, pertimbangkan untuk menghilangkan ANALYZE kata kunci. Hasilnya berisi lebih sedikit detail tetapi disediakan secara instan.
EXPLAIN (VERBOSE, BUFFERS) SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
Situs pihak ketiga, seperti explain.depesz.com dapat membantu dalam memahami rencana kueri. Beberapa pertanyaan yang harus Anda coba jawab adalah:
- Apakah kueri diparalelkan?
- Apakah indeks digunakan?
- Apakah saya menggunakan kondisi yang sama dalam klausa WHERE seperti dalam definisi indeks parsial?
- Jika saya menggunakan partisi, partisi yang tidak diperlukan dipracu?
Jika vektor Anda dinormalisasi ke panjang 1, seperti penyematan OpenAI. Anda harus mempertimbangkan untuk menggunakan produk dalam (<#>) untuk performa terbaik.
Eksekusi paralel
Dalam output rencana penjelasan Anda, cari Workers Planned dan Workers Launched (yang terakhir hanya ketika ANALYZE kata kunci digunakan). Parameter max_parallel_workers_per_gather PostgreSQL menentukan berapa banyak pekerja latar belakang yang dapat diluncurkan database untuk setiap Gather simpul paket dan Gather Merge . Meningkatkan nilai ini mungkin mempercepat kueri pencarian Anda yang tepat tanpa harus membuat indeks. Namun, perhatikan bahwa database mungkin tidak memutuskan untuk menjalankan rencana secara paralel bahkan ketika nilai ini tinggi.
EXPLAIN SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 3;
QUERY PLAN
------------------------------------------------------------------------------------------
Limit (cost=4214.82..4215.16 rows=3 width=33)
-> Gather Merge (cost=4214.82..13961.30 rows=84752 width=33)
Workers Planned: 1
-> Sort (cost=3214.81..3426.69 rows=84752 width=33)
Sort Key: ((embedding <-> '[1,2,3]'::vector))
-> Parallel Seq Scan on t_test (cost=0.00..2119.40 rows=84752 width=33)
(6 rows)
Pengindeksan
Tanpa indeks yang ada, ekstensi melakukan pencarian yang tepat, yang memberikan pengenalan sempurna dengan mengorbankan performa.
Untuk melakukan perkiraan pencarian tetangga terdekat, Anda dapat membuat indeks pada data Anda, yang berdagang pengenalan untuk performa eksekusi.
Jika memungkinkan, selalu muat data Anda sebelum mengindeksnya. Keduanya lebih cepat untuk membuat indeks dengan cara ini dan tata letak yang dihasilkan lebih optimal.
Ada tiga jenis indeks yang didukung:
- File Terbalik dengan Kompresi Rata (IVFFlat)
- Dunia Kecil yang Dapat Dinavigasi Hierarkis (HNSW)
- Perkiraan Disk Tetangga Terdekat (DiskANN)
Indeks IVFFlat memiliki waktu build yang lebih cepat dan menggunakan lebih sedikit memori daripada HNSW, tetapi memiliki performa kueri yang lebih rendah (dalam hal tradeoff pengenalan kecepatan).
DiskANN menawarkan keseimbangan yang besar dari keduanya menawarkan performa kueri yang sangat akurat dan waktu build yang cepat.
Batas
- Untuk mengindeks kolom, kolom harus memiliki dimensi yang ditentukan. Mencoba mengindeks kolom yang didefinisikan sebagai
col vectorhasil dalam kesalahan:ERROR: column does not have dimensions. - Anda hanya dapat mengindeks kolom yang memiliki hingga 2000 dimensi. Mencoba mengindeks kolom dengan lebih banyak dimensi menghasilkan kesalahan:
ERROR: column cannot have more than 2000 dimensions for INDEX_TYPE indexdi manaINDEX_TYPEadalahivfflatatauhnsw.
Meskipun Anda dapat menyimpan vektor dengan lebih dari 2000 dimensi, Anda tidak dapat mengindeksnya. Anda dapat menggunakan pengurangan dimensi agar sesuai dengan batas. Atau, mengandalkan pemartisian dan/atau sharding dengan Azure Cosmos DB for PostgreSQL untuk mencapai performa yang dapat diterima tanpa pengindeksan.
File Terbalik dengan Kompresi Datar (IVFFlat)
ivfflat adalah indeks untuk perkiraan pencarian tetangga terdekat (ANN). Metode ini menggunakan indeks file terbalik untuk mempartisi himpunan data ke dalam beberapa daftar. Parameter pemeriksaan mengontrol berapa banyak daftar yang dicari, yang dapat meningkatkan akurasi hasil pencarian dengan biaya kecepatan pencarian yang lebih lambat.
Jika parameter pemeriksaan diatur ke jumlah daftar dalam indeks, maka semua daftar dicari dan pencarian menjadi pencarian tetangga terdekat yang tepat. Dalam hal ini, perencana tidak menggunakan indeks karena mencari semua daftar setara dengan melakukan pencarian brute-force pada seluruh himpunan data.
Metode pengindeksan mempartisi himpunan data ke dalam beberapa daftar menggunakan algoritma pengklusteran k-means. Setiap daftar berisi vektor yang paling dekat dengan pusat kluster tertentu. Selama pencarian, vektor kueri dibandingkan dengan pusat kluster untuk menentukan daftar mana yang kemungkinan besar berisi tetangga terdekat. Jika parameter probe diatur ke 1, maka hanya daftar yang sesuai dengan pusat kluster terdekat yang akan dicari.
Opsi indeks
Memilih nilai yang benar untuk jumlah pemeriksaan yang akan dilakukan dan ukuran daftar dapat memengaruhi performa pencarian. Tempat yang bagus untuk memulai adalah:
- Gunakan
listssamarows / 1000dengan untuk tabel dengan hingga 1 juta baris dansqrt(rows)untuk himpunan data yang lebih besar. - Untuk
probesmemulai denganlists / 10untuk tabel hingga 1 juta baris dansqrt(lists)untuk himpunan data yang lebih besar.
Jumlah lists didefinisikan setelah pembuatan indeks dengan lists opsi :
CREATE INDEX t_test_embedding_cosine_idx ON t_test USING ivfflat (embedding vector_cosine_ops) WITH (lists = 5000);
Pemeriksaan dapat diatur untuk seluruh koneksi atau per transaksi (menggunakan SET LOCAL dalam blok transaksi):
SET ivfflat.probes = 10;
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 10 probes
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 10 probes
BEGIN;
SET LOCAL ivfflat.probes = 10;
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 10 probes
COMMIT;
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses default, one probe
Kemajuan pengindeksan
Dengan PostgreSQL 12 dan yang lebih baru, Anda dapat menggunakan pg_stat_progress_create_index untuk memeriksa kemajuan pengindeksan.
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
Fase untuk membangun indeks IVFFlat adalah:
initializingperforming k-meansassigning tuplesloading tuples
Catatan
Persentase kemajuan (%) hanya diisi selama loading tuples fase.
Dunia Kecil yang Dapat Dinavigasi Hierarkis (HNSW)
hnsw adalah indeks untuk perkiraan pencarian tetangga terdekat (ANN) menggunakan algoritma Dunia Kecil yang Dapat Dinavigasi Hierarkis. Ini bekerja dengan membuat grafik di sekitar titik masuk yang dipilih secara acak yang menemukan tetangga terdekat mereka, grafik kemudian diperluas dengan beberapa lapisan, setiap lapisan bawah yang berisi lebih banyak titik. Grafik multilayer ini ketika dicari dimulai di bagian atas, mempersempit hingga mencapai lapisan terendah yang berisi tetangga terdekat kueri.
Membangun indeks ini membutuhkan lebih banyak waktu dan memori daripada IVFFlat, namun memiliki tradeoff pengenalan kecepatan yang lebih baik. Selain itu, tidak ada langkah pelatihan seperti IVFFlat, sehingga indeks dapat dibuat pada tabel kosong.
Opsi indeks
Saat membuat indeks, Anda dapat menyetel dua parameter:
-
m- jumlah maksimum koneksi per lapisan (default ke 16) -
ef_construction- ukuran daftar kandidat dinamis yang digunakan untuk konstruksi grafik (default ke 64)
CREATE INDEX t_test_hnsw_l2_idx ON t_test USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
Selama kueri, Anda dapat menentukan daftar kandidat dinamis untuk pencarian (default ke 40).
Daftar kandidat dinamis untuk pencarian dapat diatur untuk seluruh koneksi atau per transaksi (menggunakan SET LOCAL dalam blok transaksi):
SET hnsw.ef_search = 100;
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 100 candidates
BEGIN;
SET hnsw.ef_search = 100;
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 100 candidates
COMMIT;
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses default, 40 candidates
Kemajuan pengindeksan
Dengan PostgreSQL 12 dan yang lebih baru, Anda dapat menggunakan pg_stat_progress_create_index untuk memeriksa kemajuan pengindeksan.
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
Fase untuk membangun indeks HNSW adalah:
initializingloading tuples
Perkiraan Disk Tetangga Terdekat (DiskANN)
Catatan
DiskANN hanya didukung di Server Fleksibel Azure PostgreSQL.
DiskANN adalah perkiraan algoritma pencarian tetangga terdekat yang dapat diskalakan untuk pencarian vektor yang efisien dalam skala apa pun. Ini menawarkan pengenalan tinggi, kueri tinggi per detik (QPS), dan latensi kueri rendah, bahkan untuk himpunan data miliaran titik. Ini menjadikannya alat yang kuat untuk menangani data dalam volume besar.
Pelajari selengkapnya tentang DiskANN dari Microsoft.
Membangun indeks ini membutuhkan lebih banyak waktu dan memori daripada IVFFlat, namun memiliki tradeoff pengenalan kecepatan yang lebih baik. Selain itu, tidak ada langkah pelatihan seperti IVFFlat, sehingga indeks dapat dibuat pada tabel kosong.
Opsi indeks
Saat membuat indeks dengan diskann, Anda dapat menentukan berbagai parameter untuk mengontrol perilakunya. Berikut adalah opsi yang saat ini kami miliki:
-
max_neighbors: Jumlah maksimum tepi per simpul dalam grafik. (Default ke 32) -
l_value_ib: Ukuran daftar pencarian selama build indeks (Default ke 50)
CREATE INDEX my_table_embedding_diskann_custom_idx ON my_table USING diskann (embedding vector_cosine_ops)
WITH (
max_neighbors = 48,
l_value_ib = 100
);
Nilai L untuk pemindaian indeks (l_value_is) dapat diatur untuk seluruh koneksi atau per transaksi (menggunakan SET LOCAL dalam blok transaksi):
SET diskann.l_value_is = 100;
SELECT * FROM my_table ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 100 candidates
Postgres akan secara otomatis memutuskan kapan harus menggunakan indeks DiskANN. Jika ada skenario yang selalu ingin Anda gunakan indeksnya, gunakan perintah berikut:
SET LOCAL enable_seqscan TO OFF;
SELECT * FROM my_table ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- forces the use of index
Kemajuan pengindeksan
Dengan PostgreSQL 12 dan yang lebih baru, Anda dapat menggunakan pg_stat_progress_create_index untuk memeriksa kemajuan pengindeksan.
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
Fase untuk membangun indeks DiskANN adalah:
initializingloading tuples
Memilih fungsi akses indeks
Jenis ini vector memungkinkan Anda untuk melakukan tiga jenis pencarian pada vektor yang disimpan. Anda perlu memilih fungsi akses yang benar untuk indeks Anda agar database mempertimbangkan indeks Anda saat menjalankan kueri Anda. Contoh yang ditunjukkan pada ivfflat jenis indeks, namun hal yang sama dapat dilakukan untuk hnsw indeks dan diskann . Opsi lists hanya berlaku untuk ivfflat indeks.
Jarak kosinus
Untuk pencarian kesamaan kosinus, gunakan vector_cosine_ops metode akses.
CREATE INDEX t_test_embedding_cosine_idx ON t_test USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
Untuk menggunakan indeks di atas, kueri perlu melakukan pencarian kesamaan kosinus, yang dilakukan dengan <=> operator.
EXPLAIN SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5;
QUERY PLAN
------------------------------------------------------------------------------------------------------
Limit (cost=5.02..5.23 rows=5 width=33)
-> Index Scan using t_test_embedding_cosine_idx on t_test (cost=5.02..175.06 rows=4003 width=33)
Order By: (embedding <=> '[1,2,3]'::vector)
(3 rows)
Jarak L2
Untuk jarak L2 (juga dikenal sebagai jarak Euclidean), gunakan vector_l2_ops metode akses.
CREATE INDEX t_test_embedding_l2_idx ON t_test USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
Untuk menggunakan indeks di atas, kueri perlu melakukan pencarian jarak L2, yang dilakukan dengan <-> operator.
EXPLAIN SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
QUERY PLAN
--------------------------------------------------------------------------------------------------
Limit (cost=5.02..5.23 rows=5 width=33)
-> Index Scan using t_test_embedding_l2_idx on t_test (cost=5.02..175.06 rows=4003 width=33)
Order By: (embedding <-> '[1,2,3]'::vector)
(3 rows)
Produk dalam
Untuk kesamaan vector_ip_ops produk dalam, gunakan metode akses.
CREATE INDEX t_test_embedding_ip_idx ON t_test USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
Untuk menggunakan indeks di atas, kueri perlu melakukan pencarian kesamaan produk dalam, yang dilakukan dengan <#> operator.
EXPLAIN SELECT * FROM t_test ORDER BY embedding <#> '[1,2,3]' LIMIT 5;
QUERY PLAN
--------------------------------------------------------------------------------------------------
Limit (cost=5.02..5.23 rows=5 width=33)
-> Index Scan using t_test_embedding_ip_idx on t_test (cost=5.02..175.06 rows=4003 width=33)
Order By: (embedding <#> '[1,2,3]'::vector)
(3 rows)
Indeks parsial
Dalam beberapa skenario, bermanfaat untuk memiliki indeks yang hanya mencakup sekumpulan data parsial. Kita dapat, misalnya, membangun indeks hanya untuk pengguna premium kita:
CREATE INDEX t_premium ON t_test USING ivfflat (vec vector_ip_ops) WITH (lists = 100) WHERE tier = 'premium';
Kita sekarang dapat melihat tingkat premium sekarang menggunakan indeks:
explain select * from t_test where tier = 'premium' order by vec <#> '[2,2,2]';
QUERY PLAN
------------------------------------------------------------------------------------
Index Scan using t_premium on t_test (cost=65.57..25638.05 rows=245478 width=39)
Order By: (vec <#> '[2,2,2]'::vector)
(2 rows)
Sementara pengguna tingkat gratis tidak memiliki manfaat:
explain select * from t_test where tier = 'free' order by vec <#> '[2,2,2]';
QUERY PLAN
-----------------------------------------------------------------------
Sort (cost=44019.01..44631.37 rows=244941 width=39)
Sort Key: ((vec <#> '[2,2,2]'::vector))
-> Seq Scan on t_test (cost=0.00..15395.25 rows=244941 width=39)
Filter: (tier = 'free'::text)
(4 rows)
Hanya memiliki subset data yang diindeks, berarti indeks membutuhkan lebih sedikit ruang pada disk dan lebih cepat untuk dicari.
PostgreSQL mungkin gagal mengenali bahwa indeks aman digunakan jika formulir yang digunakan dalam WHERE klausa definisi indeks parsial tidak cocok dengan yang digunakan dalam kueri Anda.
Dalam contoh himpunan data kami, kami hanya memiliki nilai 'free'yang tepat , 'test' dan 'premium' sebagai nilai yang berbeda dari kolom tingkat. Bahkan dengan kueri yang menggunakan tier LIKE 'premium' PostgreSQL tidak menggunakan indeks.
explain select * from t_test where tier like 'premium' order by vec <#> '[2,2,2]';
QUERY PLAN
-----------------------------------------------------------------------
Sort (cost=44086.30..44700.00 rows=245478 width=39)
Sort Key: ((vec <#> '[2,2,2]'::vector))
-> Seq Scan on t_test (cost=0.00..15396.59 rows=245478 width=39)
Filter: (tier ~~ 'premium'::text)
(4 rows)
Partisi
Salah satu cara untuk meningkatkan performa adalah dengan membagi himpunan data melalui beberapa partisi. Kita dapat membayangkan sistem ketika wajar untuk merujuk ke data hanya dari tahun ini atau mungkin dua tahun terakhir. Dalam sistem seperti itu, Anda dapat mempartisi data Anda dengan rentang tanggal dan kemudian memanfaatkan performa yang ditingkatkan ketika sistem hanya dapat membaca partisi yang relevan seperti yang didefinisikan oleh tahun yang dikueri.
Mari kita tentukan tabel yang dipartisi:
CREATE TABLE t_test_partitioned(vec vector(3), vec_date date default now()) partition by range (vec_date);
Kita dapat membuat partisi secara manual untuk setiap tahun atau menggunakan fungsi utilitas Citus (tersedia di Cosmos DB for PostgreSQL).
select create_time_partitions(
table_name := 't_test_partitioned',
partition_interval := '1 year',
start_from := '2020-01-01'::timestamptz,
end_at := '2024-01-01'::timestamptz
);
Periksa partisi yang dibuat:
\d+ t_test_partitioned
Partitioned table "public.t_test_partitioned"
Column | Type | Collation | Nullable | Default | Storage | Compression | Stats target | Description
----------+-----------+-----------+----------+---------+----------+-------------+--------------+-------------
vec | vector(3) | | | | extended | | |
vec_date | date | | | now() | plain | | |
Partition key: RANGE (vec_date)
Partitions: t_test_partitioned_p2020 FOR VALUES FROM ('2020-01-01') TO ('2021-01-01'),
t_test_partitioned_p2021 FOR VALUES FROM ('2021-01-01') TO ('2022-01-01'),
t_test_partitioned_p2022 FOR VALUES FROM ('2022-01-01') TO ('2023-01-01'),
t_test_partitioned_p2023 FOR VALUES FROM ('2023-01-01') TO ('2024-01-01')
Untuk membuat partisi secara manual:
CREATE TABLE t_test_partitioned_p2019 PARTITION OF t_test_partitioned FOR VALUES FROM ('2019-01-01') TO ('2020-01-01');
Kemudian pastikan kueri Anda benar-benar memfilter ke subset partisi yang tersedia. Misalnya dalam kueri di bawah ini kami memfilter ke dua partisi:
explain analyze select * from t_test_partitioned where vec_date between '2022-01-01' and '2024-01-01';
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------
Append (cost=0.00..58.16 rows=12 width=36) (actual time=0.014..0.018 rows=3 loops=1)
-> Seq Scan on t_test_partitioned_p2022 t_test_partitioned_1 (cost=0.00..29.05 rows=6 width=36) (actual time=0.013..0.014 rows=1 loops=1)
Filter: ((vec_date >= '2022-01-01'::date) AND (vec_date <= '2024-01-01'::date))
-> Seq Scan on t_test_partitioned_p2023 t_test_partitioned_2 (cost=0.00..29.05 rows=6 width=36) (actual time=0.002..0.003 rows=2 loops=1)
Filter: ((vec_date >= '2022-01-01'::date) AND (vec_date <= '2024-01-01'::date))
Planning Time: 0.125 ms
Execution Time: 0.036 ms
Anda dapat mengindeks tabel yang dipartisi.
CREATE INDEX ON t_test_partitioned USING ivfflat (vec vector_cosine_ops) WITH (lists = 100);
explain analyze select * from t_test_partitioned where vec_date between '2022-01-01' and '2024-01-01' order by vec <=> '[1,2,3]' limit 5;
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=4.13..12.20 rows=2 width=44) (actual time=0.040..0.042 rows=1 loops=1)
-> Merge Append (cost=4.13..12.20 rows=2 width=44) (actual time=0.039..0.040 rows=1 loops=1)
Sort Key: ((t_test_partitioned.vec <=> '[1,2,3]'::vector))
-> Index Scan using t_test_partitioned_p2022_vec_idx on t_test_partitioned_p2022 t_test_partitioned_1 (cost=0.04..4.06 rows=1 width=44) (actual time=0.022..0.023 rows=0 loops=1)
Order By: (vec <=> '[1,2,3]'::vector)
Filter: ((vec_date >= '2022-01-01'::date) AND (vec_date <= '2024-01-01'::date))
-> Index Scan using t_test_partitioned_p2023_vec_idx on t_test_partitioned_p2023 t_test_partitioned_2 (cost=4.08..8.11 rows=1 width=44) (actual time=0.015..0.016 rows=1 loops=1)
Order By: (vec <=> '[1,2,3]'::vector)
Filter: ((vec_date >= '2022-01-01'::date) AND (vec_date <= '2024-01-01'::date))
Planning Time: 0.167 ms
Execution Time: 0.139 ms
(11 rows)