Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
Důležité
Azure Cosmos DB for PostgreSQL se už pro nové projekty nepodporuje. Tuto službu nepoužívejte pro nové projekty. Místo toho použijte jednu z těchto dvou služeb:
Azure Cosmos DB for NoSQL můžete použít pro distribuované databázové řešení navržené pro vysoce škálovatelné scénáře s 99,999% smlouvou o úrovni služeb (SLA), okamžitým automatickým škálováním a automatickým převzetím služeb při selhání napříč několika oblastmi.
Použijte funkci Elastic Clusters služby Azure Database for PostgreSQL pro horizontálně dělené PostgreSQL pomocí opensourcového rozšíření Citus.
Rozšíření pgvector přidá do PostgreSQL hledání open source vektorové podobnosti.
Tento článek popisuje omezení a kompromisy pgvector a ukazuje, jak používat dělení, indexování a nastavení vyhledávání ke zlepšení výkonu.
Další informace o samotném rozšíření naleznete v kapitole Základy pgvector. Můžete se také obrátit na oficiální soubor README projektu.
Výkon
Vždy byste měli začít zkoumáním plánu dotazu. Pokud se dotaz přiměřeně rychle ukončí, spusťte EXPLAIN (ANALYZE,VERBOSE, BUFFERS)příkaz .
EXPLAIN (ANALYZE, VERBOSE, BUFFERS) SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
U dotazů, které trvá příliš dlouho, zvažte vyřazení klíčového ANALYZE slova. Výsledek obsahuje méně podrobností, ale poskytuje se okamžitě.
EXPLAIN (VERBOSE, BUFFERS) SELECT * FROM t_test ORDER BY embedding <-> '[1,2,3]' LIMIT 5;
Webové stránky třetích stran, jako explain.depesz.com, mohou být užitečné při chápání plánů dotazů. Mezi otázky, na které byste se měli pokusit odpovědět, patří:
- Byl dotaz paralelizovaný?
- Použil se index?
- Použil(a) jsem v klauzuli WHERE stejnou podmínku jako v částečné definici indexu?
- Pokud používám dělení, byly nepotřebné oddíly prořezány?
Pokud jsou vaše vektory normalizovány na délku 1, jako embeddingy OpenAI. Pro zajištění nejlepšího výkonu byste měli zvážit použití vnitřního produktu (<#>).
Paralelní spouštění
Ve výstupu z vysvětlení plánu vyhledejte Workers Planned a Workers Launched (druhý pouze v případě, že bylo použito klíčové slovo ANALYZE). Parametr max_parallel_workers_per_gather PostgreSQL definuje, kolik pracovních procesů na pozadí může databáze spustit pro každý uzel plánu Gather a Gather Merge. Zvýšení této hodnoty může urychlit přesné vyhledávací dotazy, aniž byste museli vytvářet indexy. Upozorňujeme však, že se databáze nemusí rozhodnout spustit plán paralelně, i když je tato hodnota vysoká.
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)
Indexování
Bez přítomnosti indexů provádí rozšíření přesné vyhledávání, které poskytuje dokonalou úplnost na úkor výkonu.
Aby bylo možné provést přibližné hledání nejbližšího souseda, můžete vytvořit indexy na vašich datech, které se obchodují s výkonem provádění.
Pokud je to možné, před indexováním je vždy načtěte. Vytvoření indexu je rychlejší a výsledné rozložení je optimální.
Existují tři podporované typy indexů:
- Invertovaný soubor s plochou kompresí (IVFFlat)
- Hierarchické navigovatelné malé světy (HNSW)
- Přibližný nejbližší soused na disku (DiskANN)
Index IVFFlat má rychlejší dobu sestavení a využívá méně paměti než HNSW, ale má nižší výkon dotazů (z hlediska kompromisu rychlosti a vzpomenutí).
DiskANN nabízí skvělou rovnováhu mezi vysoce přesným výkonem dotazů a rychlostí sestavení.
Omezení
- Aby bylo možné indexovat sloupec, musí mít definované dimenze. Pokus o indexování sloupce definovaného jako
col vectorvýsledek chyby:ERROR: column does not have dimensions. - Indexovat můžete pouze sloupec, který má až 2 000 dimenzí. Při pokusu o indexování sloupce s více dimenzemi dojde k chybě:
ERROR: column cannot have more than 2000 dimensions for INDEX_TYPE index, kdeINDEX_TYPEje buďivfflatnebohnsw.
I když můžete ukládat vektory s více než 2000 dimenzemi, nemůžete je indexovat. K přizpůsobení limitů můžete použít redukci rozměrnosti. Pokud chcete dosáhnout přijatelného výkonu bez indexování, můžete se také spolehnout na particionování a/nebo sharding pomocí služby Azure Cosmos DB for PostgreSQL.
Invertovaný soubor s plochou kompresí (IVFFlat)
ivfflat je index pro přibližné hledání nejbližších sousedů (ANN). Tato metoda používá invertovaný index souborů k rozdělení datové sady do více seznamů. Parametr sond určuje, kolik seznamů se prohledá, což může zlepšit přesnost výsledků hledání za cenu pomalejší rychlosti hledání.
Pokud je parametr sond nastaven na počet seznamů v indexu, budou prohledány všechny seznamy a hledání se stane přesným hledáním nejbližšího souseda. V tomto případě plánovač nepoužívá index, protože vyhledávání ve všech seznamech odpovídá provedení hledání hrubou silou pro celou datovou sadu.
Metoda indexování rozdělí datovou sadu do více seznamů pomocí algoritmu clusteringu k-means. Každý seznam obsahuje vektory, které jsou nejblíže konkrétnímu centru clusteru. Během hledání se vektor dotazu porovnává s středy clusteru a zjišťuje, které seznamy budou pravděpodobně obsahovat nejbližší sousedy. Pokud je parametr probes nastaven na hodnotu 1, prohledá se pouze seznam odpovídající nejbližšímu středu clusteru.
Možnosti indexu
Výběr správné hodnoty pro počet sond, které se mají provést, a velikost seznamů může mít vliv na výkon hledání. Dobrá místa, kde začít, jsou:
- Pro tabulky s až 1 milionem řádků použijte
listsrovnérows / 1000, a pro větší datové sadysqrt(rows). -
probesZačnětelists / 10u tabulek o 1 milionech řádků asqrt(lists)u větších datových sad.
lists Velikost je definována při vytvoření indexu lists s možností:
CREATE INDEX t_test_embedding_cosine_idx ON t_test USING ivfflat (embedding vector_cosine_ops) WITH (lists = 5000);
Sondy lze nastavit pro celé připojení nebo pro každou transakci (pomocí SET LOCAL v rámci bloku transakce):
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
Průběh indexování
S PostgreSQL 12 a novějšími můžete pomocí pg_stat_progress_create_index sledovat průběh indexování.
SELECT phase, round(100.0 * tuples_done / nullif(tuples_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
Fáze vytváření indexů IVFFlat jsou:
initializingperforming k-meansassigning tuplesloading tuples
Poznámka:
Procento průběhu (%) je vyplněno pouze během loading tuples fáze.
Hierarchické navigace v malých světech (HNSW)
hnsw je index pro vyhledávání přibližného nejbližšího souseda (ANN) pomocí algoritmu hierarchických navigovatelných malých světů. Funguje to tak, že vytvoří graf kolem náhodně vybraných vstupních bodů, které najdou nejbližší sousedy, graf se pak rozšíří o více vrstev, přičemž každá nižší vrstva obsahuje více bodů. Tento vícevrstvý graf při hledání začíná nahoře a zúží se, dokud se nedotkne nejnižší vrstvy, která obsahuje nejbližší sousedy dotazu.
Sestavení tohoto indexu trvá více času a paměti než u IVFFlat, ale má lepší poměr mezi rychlostí a schopností vybavení paměti. Kromě toho neexistuje žádný krok trénování jako u IVFFlat, takže index lze vytvořit v prázdné tabulce.
Možnosti indexu
Při vytváření indexu můžete ladit dva parametry:
-
m– maximální počet připojení na vrstvu (výchozí hodnota je 16) -
ef_construction- velikost dynamického kandidátního seznamu použitého pro vytváření grafů (výchozí hodnota je 64)
CREATE INDEX t_test_hnsw_l2_idx ON t_test USING hnsw (embedding vector_l2_ops) WITH (m = 16, ef_construction = 64);
Během dotazů můžete zadat seznam dynamických kandidátů pro vyhledávání (výchozí hodnota je 40).
Seznam dynamických kandidátů pro vyhledávání lze nastavit pro celé připojení nebo pro každou transakci (pomocí SET LOCAL v rámci bloku transakce):
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
Průběh indexování
S PostgreSQL 12 a novějšími můžete pomocí pg_stat_progress_create_index sledovat průběh indexování.
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
Fáze vytváření indexů HNSW jsou:
initializingloading tuples
Přibližný nejbližší soused disku (DiskANN)
Poznámka:
DiskANN se podporuje jenom na flexibilním serveru Azure PostgreSQL.
DiskANN je škálovatelný přibližný vyhledávací algoritmus nejbližšího souseda pro efektivní vektorové vyhledávání v libovolném měřítku. Nabízí vysokou úplnost, vysoké dotazy za sekundu (QPS) a nízkou latenci dotazů, a to i pro datové sady s miliardami bodů. Díky tomu je výkonný nástroj pro zpracování velkých objemů dat.
Přečtěte si další informace o DiskANN od Microsoftu.
Sestavení tohoto indexu trvá více času a zabírá více paměti než IVFFlat, avšak nabízí lepší kompromis mezi rychlostí a přesností vyhledávání. Kromě toho neexistuje žádný krok trénování jako u IVFFlat, takže index lze vytvořit v prázdné tabulce.
Možnosti indexu
Při vytváření indexu pomocí diskannmůžete zadat různé parametry pro řízení jeho chování. Tady jsou možnosti, které aktuálně máme:
-
max_neighbors: Maximální počet hran na uzel v grafu. (Výchozí hodnota je 32) -
l_value_ib: Velikost vyhledávacího seznamu během sestavení indexu (výchozí hodnotou je 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
);
Hodnotu L pro prohledávání indexu (l_value_is) lze nastavit pro celé připojení nebo pro každou transakci (pomocí SET LOCAL v rámci bloku transakce):
SET diskann.l_value_is = 100;
SELECT * FROM my_table ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- uses 100 candidates
Postgres se automaticky rozhodne, kdy se má použít index DiskANN. Pokud existují scénáře, které chcete vždy použít index, použijte následující příkaz:
SET LOCAL enable_seqscan TO OFF;
SELECT * FROM my_table ORDER BY embedding <=> '[1,2,3]' LIMIT 5; -- forces the use of index
Průběh indexování
S PostgreSQL 12 a novějšími můžete pomocí pg_stat_progress_create_index sledovat průběh indexování.
SELECT phase, round(100.0 * blocks_done / nullif(blocks_total, 0), 1) AS "%" FROM pg_stat_progress_create_index;
Fáze vytváření indexů DiskANN jsou:
initializingloading tuples
Výběr funkce pro přístup k indexu
Tento vector typ umožňuje provádět tři typy hledání u uložených vektorů. Aby databáze brala při provádění vašich dotazů v úvahu váš index, musíte pro svůj index vybrat správnou přístupovou funkci. Příklady demonstrují typy ivfflat indexů, ale totéž lze provést pro hnsw a diskann indexy. Tato lists možnost se vztahuje pouze na ivfflat indexy.
Kosinusová vzdálenost
Pro vyhledávání kosinové podobnosti použijte metodu přístupu vector_cosine_ops.
CREATE INDEX t_test_embedding_cosine_idx ON t_test USING ivfflat (embedding vector_cosine_ops) WITH (lists = 100);
Pokud chcete použít výše uvedený index, musí dotaz provést kosinusové vyhledávání podobnosti, které se provádí s operátorem <=> .
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)
Vzdálenost L2
Pro vzdálenost L2 (označovanou také jako Euclidean vzdálenost) použijte metodu vector_l2_ops přístupu.
CREATE INDEX t_test_embedding_l2_idx ON t_test USING ivfflat (embedding vector_l2_ops) WITH (lists = 100);
Pokud chcete použít výše uvedený index, musí dotaz provést hledání vzdálenosti L2, které se provádí s operátorem <-> .
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)
Vnitřní produkt
Pro vnitřní podobnost produktu použijte metodu vector_ip_ops přístupu.
CREATE INDEX t_test_embedding_ip_idx ON t_test USING ivfflat (embedding vector_ip_ops) WITH (lists = 100);
Pokud chcete použít výše uvedený index, musí dotaz provést vnitřní vyhledávání podobnosti produktu, které se provádí s operátorem <#> .
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)
Částečné indexy
V některých scénářích je výhodné mít index, který pokrývá pouze částečnou sadu dat. Můžeme například vytvořit index pouze pro naše prémiové uživatele:
CREATE INDEX t_premium ON t_test USING ivfflat (vec vector_ip_ops) WITH (lists = 100) WHERE tier = 'premium';
Teď vidíme, že úroveň Premium teď používá index:
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)
I když uživatelé úrovně Free nemají výhodu:
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)
Když máte indexovanou jenom podmnožinu dat, znamená to, že index na disku zabírá méně místa a je rychlejší prohledávat.
PostgreSQL nemusí rozpoznat, že index je bezpečný, pokud se formulář použitý v WHERE klauzuli částečné definice indexu neshoduje s formulářem použitým v dotazech.
V naší ukázkové datové sadě máme pouze přesné hodnoty 'free''test' a 'premium' jako jedinečné hodnoty sloupce vrstvy. I s dotazem, který používá tier LIKE 'premium', PostgreSQL index nepoužívá.
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)
dělení na části
Jedním ze způsobů, jak zlepšit výkon, je rozdělit datovou sadu na více oddílů. Můžeme si představit systém, když je přirozené odkazovat pouze na data z aktuálního roku nebo možná z předchozích dvou let. V takovém systému můžete data rozdělit podle rozsahu kalendářních dat a pak využít vyššího výkonu, když systém dokáže číst jenom relevantní oddíly definované dotazovaným rokem.
Pojďme definovat dělenou tabulku:
CREATE TABLE t_test_partitioned(vec vector(3), vec_date date default now()) partition by range (vec_date);
Oddíly můžeme ručně vytvořit pro každý rok nebo použít funkci nástroje Citus (k dispozici ve službě 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
);
Zkontrolujte vytvořené oddíly:
\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')
Jak ručně vytvořit oddíl:
CREATE TABLE t_test_partitioned_p2019 PARTITION OF t_test_partitioned FOR VALUES FROM ('2019-01-01') TO ('2020-01-01');
Pak se ujistěte, že se vaše dotazy skutečně filtrují na podmnožinu dostupných oddílů. Například v následujícím dotazu jsme vyfiltrovali až dva oddíly:
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
Dělenou tabulku můžete indexovat.
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)
Závěr
Blahopřejeme, právě jste se naučili kompromisy, omezení a osvědčené postupy pro dosažení nejlepšího výkonu s pgvector.