Aracılığıyla paylaş


PostgreSQL için Azure Veritabanı - Esnek Sunucuda kullanırken pgvector performansı iyileştirme

ŞUNLAR IÇIN GEÇERLIDIR: PostgreSQL için Azure Veritabanı - Esnek Sunucu

Uzantı, pgvector esnek PostgreSQL için Azure Veritabanı sunucuya açık kaynak vektör benzerlik araması ekler.

Bu makalede, performansı geliştirmek için bölümleme, dizin oluşturma ve arama ayarlarının nasıl kullanılacağına ilişkin sınırlamalar ve dezavantajlar pgvector ele alınıyor.

Uzantının kendisi hakkında daha fazla bilgi için temel bilgilerine pgvectorbakın. Projenin resmi BENİOKU bölümüne de başvurmak isteyebilirsiniz.

Performans

Her zaman sorgu planını araştırarak başlamalısınız. Sorgunuz makul bir şekilde hızlı sonlandırılırsa komutunu çalıştırın EXPLAIN (ANALYZE,VERBOSE, BUFFERS).

EXPLAIN (ANALYZE, VERBOSE, BUFFERS) SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 5;

Yürütülmesi çok uzun süren sorgular için anahtar sözcüğünü ANALYZE bırakmalısınız. Sonuç daha az ayrıntı içerir ancak anında sağlanır.

EXPLAIN (VERBOSE, BUFFERS) SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 5;

explain.depesz.com gibi üçüncü taraf siteler sorgu planlarını anlamanıza yardımcı olabilir. Yanıtlamaya çalışmanız gereken bazı sorular şunlardır:

Vektörleriniz OpenAI eklemeleri gibi 1. uzunlukta normalleştirilmişse. En iyi performans için iç ürünü (<#>) kullanmayı düşünmelisiniz.

Paralel yürütme

Açıklama planınızın çıktısında ve Workers Launched (yalnızca ANALYZE anahtar sözcük kullanıldığında ikinci) öğesini arayınWorkers Planned. max_parallel_workers_per_gather PostgreSQL parametresi, veritabanının her Gather ve Gather Merge plan düğümü için kaç arka plan çalışanı başlatabileceğini tanımlar. Bu değeri artırmak dizin oluşturmak zorunda kalmadan tam arama sorgularınızı hızlandırabilir. Ancak, bu değer yüksek olduğunda bile veritabanının planı paralel olarak çalıştırmaya karar vermeyebileceğini unutmayın.

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)

Dizinleme

Dizinler mevcut olmadığında, uzantı tam bir arama gerçekleştirir ve bu da performans pahasına mükemmel bir geri çağırma sağlar.

Yaklaşık en yakın komşu araması gerçekleştirmek için verilerinizde dizinler oluşturabilirsiniz ve bu da yürütme performansının geri çağrılmasıyla sonuçlanabilir.

Mümkün olduğunda, verilerinizi dizine eklemeden önce her zaman yükleyin. Hem dizini bu şekilde oluşturmak daha hızlıdır hem de sonuçta elde edilen düzen daha uygun olur.

Desteklenen iki dizin türü vardır:

Dizin IVFFlat daha hızlı derleme sürelerine sahiptir ve değerinden HNSWdaha az bellek kullanır, ancak daha düşük sorgu performansına sahiptir (hızlı geri çağırma dengeleri açısından).

Sınırlar

  • Bir sütunun dizinini oluşturmak için tanımlanmış boyutları olması gerekir. Olarak tanımlanan col vector bir sütunu dizine ekleme girişimi şu hatayla sonuçlanır: ERROR: column does not have dimensions.
  • Yalnızca en fazla 2000 boyutu olan bir sütunun dizinini oluşturabilirsiniz. Daha fazla boyuta sahip bir sütunu dizine ekleme girişimi şu hatayla sonuçlanır: ERROR: column cannot have more than 2000 dimensions for INDEX_TYPE index burada INDEX_TYPE veya hnswolurivfflat.

2000'den fazla boyuta sahip vektörleri depolayabilirsiniz ancak bunları dizine ekleyemezsiniz. Boyutsallığı azaltmayı sınırlara sığacak şekilde kullanabilirsiniz. Alternatif olarak dizin oluşturma olmadan kabul edilebilir performans elde etmek için PostgreSQL için Azure Cosmos DB ile bölümleme ve/veya parçalama işlemlerine de güvenebilirsiniz.

Düz Sıkıştırmalı Ters Dosya (IVVFlat)

, ivfflat yaklaşık en yakın komşu (ANN) araması için bir dizindir. Bu yöntem, veri kümesini birden çok liste halinde bölümlendirmek için ters dosya dizini kullanır. Yoklamalar parametresi, kaç liste aranabileceğini denetler ve bu da arama sonuçlarının doğruluğunu daha düşük arama hızı karşılığında iyileştirebilir.

Yoklamalar parametresi dizindeki liste sayısına ayarlanırsa, tüm listeler aranırsa ve arama tam olarak en yakın komşu aramasına dönüşür. Bu durumda, tüm listelerde arama yapmak veri kümesinin tamamında deneme yanılma araması yapmaya eşdeğer olduğundan planlayıcı dizini kullanmıyordur.

Dizin oluşturma yöntemi, k ortalamaları kümeleme algoritmasını kullanarak veri kümesini birden çok liste halinde bölümler. Her liste, belirli bir küme merkezine en yakın vektörleri içerir. Arama sırasında, en yakın komşuları içerme olasılığının en yüksek olduğu listeleri belirlemek için sorgu vektöru küme merkezleriyle karşılaştırılır. Yoklamalar parametresi 1 olarak ayarlanırsa, yalnızca en yakın küme merkezine karşılık gelen liste aranırdı.

Dizin seçenekleri

Gerçekleştirilecek yoklama sayısı için doğru değerin seçilmesi ve listelerin boyutları arama performansını etkileyebilir. Başlamak için iyi yerler şunlardır:

  1. 1 milyon satıra rows / 1000 kadar olan tablolar ve sqrt(rows) daha büyük veri kümeleri için eşittir seçeneğini kullanınlists.
  2. lists / 10 Başlangıç probes olarak 1 milyon satıra kadar olan tablolar ve sqrt(lists) daha büyük veri kümeleri için.

lists Miktarı, dizin oluşturma işleminde şu lists seçenekle tanımlanır:

CREATE INDEX t_test_embedding_cosine_idx ON t_test USING ivfflat (embedding vector_cosine_ops) WITH (lists = 5000);

Yoklamalar tüm bağlantı için veya işlem başına ayarlanabilir (işlem bloğu içinde kullanılarak SET LOCAL ):

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

Dizin oluşturma ilerleme durumu

PostgreSQL 12 ve daha yeni sürümlerle dizin oluşturma işleminin ilerleme durumunu denetlemek için kullanabilirsiniz pg_stat_progress_create_index .

SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;

IVFFlat dizinleri oluşturma aşamaları şunlardır:

  1. initializing
  2. performing k-means
  3. assigning tuples
  4. loading tuples

Not

İlerleme yüzdesi (%) yalnızca aşama sırasında loading tuples doldurulur.

Hiyerarşik Gezinilebilir Küçük Dünyalar (HNSW)

hnsw Hiyerarşik Gezinilebilir Küçük Dünyalar algoritmasını kullanarak yaklaşık en yakın komşu (ANN) araması için bir dizindir. Rastgele seçilen giriş noktalarının çevresinde bir graf oluşturarak en yakın komşularını bularak çalışır, graf daha sonra her alt katman daha fazla nokta içeren birden çok katmanla genişletilir. Arama yapıldığında bu çok katmanlı grafik üstten başlar ve sorgunun en yakın komşularını içeren en düşük katmana ulaşana kadar daraltılır.

Bu dizinin oluşturulması IVFFlat'tan daha fazla zaman ve bellek gerektirir, ancak daha iyi bir hız-geri çağırma dezavantajı vardır. Ayrıca IVFFlat gibi bir eğitim adımı olmadığından dizin boş bir tabloda oluşturulabilir.

Dizin seçenekleri

Dizini oluştururken iki parametre ayarlayabilirsiniz:

  1. m - katman başına en fazla bağlantı sayısı (varsayılan olarak 16'dır)
  2. ef_construction - Grafik oluşturma için kullanılan dinamik aday listesinin boyutu (varsayılan olarak 64'tür)
CREATE INDEX t_test_hnsw_l2_idx ON t_test USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);

Sorgular sırasında, arama için dinamik aday listesini belirtebilirsiniz (varsayılan olarak 40'tır).

Arama için dinamik aday listesi, bağlantının tamamı veya işlem başına ayarlanabilir (işlem bloğu içinde kullanılarak SET LOCAL ):

SET hnsw.ef_search = 100;
SELECT * FROM t_test ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 100 candidates
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

Dizin oluşturma ilerleme durumu

PostgreSQL 12 ve daha yeni sürümlerle dizin oluşturma işleminin ilerleme durumunu denetlemek için kullanabilirsiniz pg_stat_progress_create_index .

SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;

HNSW dizinleri oluşturma aşamaları şunlardır:

  1. initializing
  2. loading tuples

Dizin erişim işlevini seçme

türü, vector depolanan vektörlerde üç tür arama gerçekleştirmenizi sağlar. Sorgularınızı yürütürken veritabanının dizininizi dikkate alabilmesi için dizininiz için doğru erişim işlevini seçmeniz gerekir. Örneklerde dizin türleri gösterilmektedir ivfflat , ancak dizinler için hnsw de aynı işlem yapılabilir. seçeneği lists yalnızca dizinler için ivfflat geçerlidir.

Kosinüs uzaklığı

Kosinüs benzerlik araması için erişim yöntemini kullanın vector_cosine_ops .

CREATE INDEX t_test_embedding_cosine_idx ON t_test USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);

Yukarıdaki dizini kullanmak için sorgunun işleçle yapılan bir kosinüs benzerliği araması gerçekleştirmesi <=> gerekir.

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)

L2 uzaklığı

L2 uzaklığı (Öklid uzaklığı olarak da bilinir) için erişim yöntemini kullanın vector_l2_ops .

CREATE INDEX t_test_embedding_l2_idx ON t_test USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);

Yukarıdaki dizini kullanmak için sorgunun işleciyle yapılan bir L2 uzaklık araması gerçekleştirmesi <-> gerekir.

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)

İç ürün

İç ürün benzerliği için erişim yöntemini kullanın vector_ip_ops .

CREATE INDEX t_test_embedding_ip_idx ON t_test USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);

Yukarıdaki dizini kullanmak için sorgunun işleciyle yapılan bir iç ürün benzerliği araması gerçekleştirmesi <#> gerekir.

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)

Kısmi dizinler

Bazı senaryolarda, verilerin yalnızca kısmi bir kümesini kapsayan bir dizine sahip olmak yararlıdır. Örneğin, yalnızca premium kullanıcılarımız için bir dizin oluşturabiliriz:

CREATE INDEX t_premium ON t_test USING ivfflat (vec vector_ip_ops) WITH (lists = 100) WHERE tier = 'premium';

Artık premium katmanın dizini kullandığını görebiliriz:

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)

Ücretsiz katman kullanıcıları avantajdan yoksun olsa da:

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)

Verilerin yalnızca bir alt kümesinin dizine sahip olması, dizinin diskte daha az yer kaplayıp aramanın daha hızlı olduğu anlamına gelir.

Kısmi dizin tanımının yan tümcesinde WHERE kullanılan form sorgularınızda kullanılan formla eşleşmiyorsa PostgreSQL dizinin güvenli olduğunu algılayamaz. Örnek veri kümemizde yalnızca tam değerlerine 'free''test' ve 'premium' katman sütununun benzersiz değerlerine sahibiz. PostgreSQL kullanan tier LIKE 'premium' bir sorguda bile dizin kullanılmaz.

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)

Bölümleme

Performansı artırmanın bir yolu, veri kümesini birden çok bölüme bölmektir. Yalnızca geçerli yıla veya belki de son iki yıla ait verilere başvurmanın doğal olduğu bir sistem hayal edebiliriz. Böyle bir sistemde, verilerinizi bir tarih aralığına göre bölümleyebilir ve ardından sistem sorgulanan yıl tarafından tanımlandığı şekilde yalnızca ilgili bölümleri okuyabildiğinde geliştirilmiş performansa göre büyük harf kullanabilirsiniz.

Bölümlenmiş bir tablo tanımlayalım:

CREATE TABLE t_test_partitioned(vec vector(3), vec_date date default now()) partition by range (vec_date);

Her yıl için el ile bölümler oluşturabilir veya Citus yardımcı programı işlevini kullanabiliriz (PostgreSQL için Cosmos DB'de kullanılabilir).

    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
    );

Oluşturulan bölümleri denetleyin:

\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')

El ile bölüm oluşturmak için:

CREATE TABLE t_test_partitioned_p2019 PARTITION OF t_test_partitioned FOR VALUES FROM ('2019-01-01') TO ('2020-01-01');

Ardından sorgularınızın aslında kullanılabilir bölümlerin bir alt kümesine göre filtrelediğinden emin olun. Örneğin aşağıdaki sorguda iki bölüme filtreledik:

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

Bölümlenmiş bir tablonun dizinini oluşturabilirsiniz.

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)

Sonraki adımlar

Tebrikler, ile pgvectoren iyi performansı elde etmek için dezavantajları, sınırlamaları ve en iyi yöntemleri öğrendin.