當結構化資料與非結構化資料需要同時檢索時,您可以使用AnalyticDB for PostgreSQL向量資料庫的混合查詢,既支援結構化欄位過濾,也支援半結構化欄位過濾,同時支援和文字欄位的全文檢索索引一起進行雙路召回。
本文適用於AnalyticDB for PostgreSQL6.0版和7.0版的雙路召回,然而7.0版更推薦您查看和使用向量檢索與全文檢索索引雙路召回。
混合檢索簡介
ANNS(Approximate Nearest Neighbors Search,近似最近鄰搜尋)向量索引只能解決非結構化資料檢索的問題。但是實際生產環境中,經常會遇到一些結構化資料與非結構化資料需要同時檢索的情境。例如需要查詢特定人員在指定時間範圍內是否出現在特定地區內,這裡的結構化條件就是時間範圍和地區範圍,非結構化條件就是人臉。因此阿里雲在AnalyticDB PostgreSQL版中加入了結構化資料與非結構化資料混合查詢的能力。
業界在解決混合查詢的問題時,一般採用兩個系統,結構化資料存在資料庫中,非結構化資料存在向量檢索系統中;然後對兩個系統求交集後再彙總得到最終的結果。這種方法一般先採用向量檢索系統取出經過放大的amp(放大係數) * topk個結果,然後再通過結構化索引過濾得到最終結果。但該方法因為結構化索引過濾結果比較多,導致召回出現比較大的損失,從而無法滿足最終需要topk個結果的要求。下面以結構化欄位和非結構化欄位的混合查詢來講解混合查詢的實現原理。
混合檢索實現原理
AnalyticDB PostgreSQL版將向量檢索引擎FastANN作為索引外掛程式整合到資料庫中,可以在使用向量檢索的同時使用結構化和半結構化索引。具體是通過最佳化器的RBO(Ranked-Biased Overlap)代價估算規則,產生不同的執行計畫來解決結構化資料與非結構化資料需要同時檢索的問題。
下文通過一個例子來說明混合檢索的原理。
假設有一個帶條件的拍圖尋找商品需求:尋找與輸入圖片相似性最高,價格在100到200元之間,並且上架時間在最近一個月以內的前100件商品。
表和索引結構如下:
CREATE TABLE products (
id serial primary key,
name varchar(256),
price real,
inTime timestamp,
url varchar(256),
feature real[]
);
-- 設定向量列為內聯模式。
ALTER TABLE products ALTER COLUMN feature SET STORAGE PLAIN;
-- 為結構化欄位建立BTREE索引。
CREATE INDEX ON products(price, intime);
-- 為向量欄位建立向量索引。
CREATE INDEX ON products USING ann(feature) WITH (dim=512);
-- 收集統計資訊,用於產生最優的混合查詢執行計畫。
ANALYZE products;採用混合查詢的SQL如下:
SELECT id, price FROM products WHERE
price > 100 AND price <=200
AND inTime > '2019-03-01 00:00:00' AND inTime <= '2019-03-31 00:00:00'
ORDER BY
feature <-> array[10,2.0,…, 512.0]
LIMIT 100;為了執行這條SQL,依據結構化條件列(price以及inTime)上是否有索引,以及結構化條件的選擇率大小不同,AnalyticDB PostgreSQL版可能會產生如下3類不同的執行計畫,並在其中選擇最優的一個執行計畫,以滿足使用者對效能和召回的要求:
第一類:暴力查詢:
根據結構化條件擷取所有合格行,再在其中根據向量距離進行排序,選擇距離最小的100條資料進行輸出。這種執行計畫召回為100%,但是查詢速度最慢,在底庫過大或符合結構化條件行數過多時查詢效能低下。執行計畫如下所示(簡化了部分輸出資訊):
QUERY PLAN ----------------------------------------------------------------------------------- LIMIT -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: (l2_squared_distance($0, feature)) -> LIMIT -> Sort Sort Key: (l2_squared_distance($0, feature)) -> Index Scan using products_price_idx on products Index Cond: 價格過濾條件 Filter: 時間過濾條件 Optimizer: Postgres query optimizer第二類:純向量查詢 + 結構化過濾:
為了加速查詢,先使用向量索引查詢出與輸入圖片最接近的N行資料,再在其中根據結構化條件進行過濾。這種執行計畫的優點是查詢速度最快,缺點是如果結構化查詢的篩選率太小(即一行資料通過過濾的機率太小),則查詢最終輸出的資料行數可能比使用者要求的行數少。執行計畫如下所示(簡化了部分輸出資訊):
QUERY PLAN ----------------------------------------------------------------------------------- LIMIT -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((feature <-> $0)) -> LIMIT -> Ann Index Scan using products_feature_idx on products ORDER BY: (feature <-> $0) Filter: 價格和時間列過濾條件 Optimizer: Postgres query optimizer第三類:向量結構化混合查詢:
向量結構化混合查詢結合了第一類和第二類執行計畫,既能使用索引,又能解決第二類執行計畫返回資料變少的問題。同時,如果結構化條件列上有索引,且索引類型支援Bitmap(左子樹)產生,則混合查詢還可以利用其他索引來產生Bitmap,從而進一步加速混合查詢。執行計畫如下所示(簡化了部分輸出資訊):
QUERY PLAN ----------------------------------------------------------------------------------- LIMIT -> Gather Motion 3:1 (slice1; segments: 3) Merge Key: ((feature <-> $0)) -> LIMIT -> Fusion Ann Scan -> Bitmap Index Scan on products_price_idx Index Cond: 價格過濾條件 -> Ann Index Scan with filter using products_feature_idx on products ORDER BY: (feature <-> $0) Filter: 時間過濾條件 Optimizer: Postgres query optimizer上述執行計畫中包含了Fusion Ann Scan節點或者Ann Index Scan with filter節點的執行計畫都是使用了混合查詢能力。這兩種執行計畫說明如下:
Ann Index Scan with filter:此節點的作用是將過濾條件下壓到向量索引內部,在索引的執行過程中同時考慮過濾條件。
Fusion Ann Scan:此節點在某些結構化條件列上有索引時可能出現,其作用是根據結構化條件產生Bitmap(左子樹),將Bitmap下壓到向量索引(右子樹)中,加速結構化條件的計算。出現Fusion Ann Scan節點時,其右子樹可能是Ann Index Scan也可能是Ann Index Scan with filter。如果右子樹是Ann Index Scan,說明除了Bitmap外沒有其他結構化條件下壓。反之會出現Ann Index Scan with filter。
混合檢索使用方法
AnalyticDB PostgreSQL版向量資料庫混合查詢既支援結構化欄位過濾,也支援半結構化欄位過濾,同時也支援和文字欄位的全文檢索索引一起進行雙路召回。混合查詢具體可以劃分為三類:
向量查詢和結構化欄位過濾組成的混合查詢。當前支援的結構化欄位類型包括:bigint、boolean、bytea、char、varchar、integer、float、double、date、smallint、timestamp和serial等所有結構化欄位,這些結構化欄位可以通過建立預設的BTREE索引在混合查詢中進行加速。結構化欄位類型的詳細資料請參見資料類型。
向量查詢和半結構化欄位過濾組成的混合查詢。當前支援的半結構化類型包括:JSON、JSONB和Array等所有半結構化欄位,這些結構化欄位可以通過建立GIN索引在混合查詢中進行加速。半結構化類型的詳細資料請參見JSON & JSONB 資料類型和Array數群組類型。
向量查詢和全文檢索索引組成的雙路召回。
在混合檢索實現原理中,已經介紹了混合查詢中對結構化欄位類型的加速最佳化方式,下面舉例說明半結構化欄位在混合檢索中的使用,以及向量檢索與全文檢索索引進行雙路召回的使用。
半結構化欄位在混合檢索中的使用
假設某證券公司有一個股票分析文章的文本庫stock_analysis_chunks,這個文本庫主要包括以下欄位:
欄位 | 類型 | 說明 |
id | serial | 編號。 |
chunk | varchar(1024) | 股票分析文章切塊後的文字區塊。 |
release_time | timestamp | 股票分析文章的發布時間。 |
stock_id_list | char(10)[] | 該股票分析文章涉及的公司股票ID列表。 |
url | varchar(1024) | 文字區塊所屬文章的連結。 |
feature | real[] | 文字區塊的embedding向量。 |
建立文本庫表stock_analysis_chunks。
CREATE TABLE stock_analysis_chunks ( id serial primary key, chunk varchar(1024), release_time timestamp, stock_id_list integer[], url varchar(1024), feature real[] ) distributed by (id);將向量列設定為內聯模式。
ALTER TABLE stock_analysis_chunks ALTER COLUMN feature SET STORAGE PLAIN;對向量列建立向量索引。
CREATE INDEX ON stock_analysis_chunks USING ann(feature) WITH (dim=1536, distancemeasure=cosine, hnsw_m=64, pq_enable=1, hnsw_ef_construction=128);對混合查詢關聯的結構化與半結構化列建立索引。
-- 對結構化欄位建立BTREE索引 CREATE INDEX ON stock_analysis_chunks(release_time); -- 對半結構欄位建立GIN索引 CREATE INDEX ON stock_analysis_chunks USING gin(stock_id_list);收集統計資訊。
ANALYZE stock_analysis_chunks;進行混合查詢。
例如需要尋找最近一個月包含某常值內容,並且涉及特定公司的股票分析文章,可以採用如下SQL進行混合查詢:
SELECT id,url, cosine_similarity(feature, array[1.0, 2.0, ..., 1536.0]::real[]) as score FROM stock_analysis_chunks WHERE release_time >= '2023-07-18 00:00:00' AND release_time <= '2023-08-18 00:00:00' AND stock_id_list @> ARRAY['BABA', 'AAPL', 'MSFT', 'AMZN'] ORDER BY feature <=> array[1.0, 2.0, ..., 1536.0]::real[] LIMIT 10;
向量檢索與全文檢索索引的雙路召回
雙路召回是指同時使用向量檢索和全文檢索索引召回資料的策略。在大多數情境下,僅使用向量檢索能力就能在相似性召回中獲得較高的召回率。然而,在某些特定情況下,例如Embedding模型表現不佳,或者因查詢複雜導致產生的向量與目標資料之間的距離較遠時,僅使用向量相似召回無法達到預期效果。這時為了提高召回率,可以採用雙路召回來豐富召回的策略。
AnalyticDB for PostgreSQL的雙路召回通過向量檢索和全文檢索索引分別召回部分資料,然後再做精排蒸餾,後處理等以獲得更佳的召回效果。
操作步驟
建立樣本表。
-- vector欄位為向量,to_tsvector為全文分詞 CREATE TABLE IF NOT EXISTS documents( id TEXT, docname TEXT, title TEXT, vector real[], text TEXT, to_tsvector TSVECTOR);將向量列設定為內聯模式。
ALTER TABLE documents ALTER COLUMN vector SET STORAGE PLAIN;建立索引。
為向量欄位建立向量索引。
CREATE INDEX ON documents USING ann(vector) WITH (dim=1024, distancemeasure=cosine, hnsw_m=64, pq_enable=1, hnsw_ef_construction=128);為全文分詞欄位建立GIN索引。
CREATE INDEX ON documents USING gin(to_tsvector);
建立依賴函數。
CREATE OR REPLACE FUNCTION public.to_tsquery_from_text(txt text, lang regconfig DEFAULT 'english'::regconfig) RETURNS tsquery LANGUAGE sql IMMUTABLE STRICT AS $function$ SELECT to_tsquery(lang, COALESCE(string_agg(split_part(word, ':', 1), ' | '), '')) FROM (SELECT UNNEST(STRING_TO_ARRAY(TO_TSVECTOR(lang, txt)::text, ' ')) AS TEXT) AS words_only;$function$雙路召回查詢。
WITH combined AS ( ( SELECT id, docname, text, cosine_similarity(densevec,ARRAY{embedding}::real[]) AS similarity, 1 AS source FROM documents ORDER BY vector <=> ARRAY[10,2.0,…, 1024.0] LIMIT 10 ) UNION ALL ( SELECT id, docname, text, ts_rank(to_tsvector, to_tsquery_from_text('{query.query}', 'zh_cn'), 32) AS similarity, 2 AS source FROM documents WHERE to_tsvector@@to_tsquery_from_text('{query.query}', 'zh_cn') ORDER BY similarity DESC LIMIT 10 ) ) SELECT id, docname, title, text, MAX(similarity) AS similarity, BIT_OR(source) AS source FROM combined GROUP BY id ORDER BY similarity DESC;UNION的第一個部分通過向量召回10條,第二個部分則通過全文檢索索引召回10條;然後再根據id進行去重操作。上述雙路召回的SQL會返回小於等於20條資料的結果集。