Hologres中的向量計算功能可以應用于相似度搜索、圖像檢索、場景識別等多種場景。通過靈活應用向量計算,可以提升數據處理和分析的效果,并實現更精準的搜索和推薦功能。本文為您介紹在Hologres中使用Proxima進行向量計算的方法及完整示例。
操作步驟
連接Hologres。
通過開發工具連接Hologres,詳情請參見連接開發工具。如果是JDBC連接,請使用Prepare Statement模式。
安裝Proxima插件。
Proxima是以一個Extension的方式與Hologres連通,因此在使用之前需要Superuser執行如下命令安裝Proxima插件。
--安裝Proxima插件 CREATE EXTENSION proxima;
Proxima插件是針對數據庫級別使用的,一個數據庫僅需要安裝一次即可,如需卸載Extension請執行如下命令。
DROP EXTENSION proxima;
重要不推薦使用
DROP EXTENSION <extension_name> CASCADE;
命令級聯卸載Extension。CASCADE(級聯)刪除命令不僅會刪除指定擴展本身,還會一并清除擴展數據(例如PostGIS數據、RoaringBitmap數據、Proxima數據、Binlog數據、BSI數據等)以及依賴該擴展的對象(包括元數據、表、視圖、Server數據等)。創建向量表和向量索引。
向量在Hologres中一般用FLOAT4數組表示,創建向量表的語法如下。
說明僅列存、行列共存表支持向量索引,行存表不支持。
定義向量時,數組維度僅支持定義為
1
,即array_ndims
、array_length
的第二入參都必須設置為1
。Hologres V2.0.11版本起,支持先導入數據、再創建向量索引,無需對compaction過程中的文件構建向量索引,縮短索引創建時間。
先創建向量索引、再導入數據:適用于實時數據場景。
--設置單個索引 BEGIN; CREATE TABLE feature_tb ( id bigint, feature_col float4[] CHECK(array_ndims(feature_col) = 1 AND array_length(feature_col, 1) = <value>) --定義向量 ); CALL set_table_property( 'feature_tb', 'proxima_vectors', '{"<feature_col>":{"algorithm":"Graph", "distance_method":"<value>", "builder_params":{"min_flush_proxima_row_count" : 1000, "min_compaction_proxima_row_count" : 1000, "max_total_size_to_merge_mb" : 2000}}}'); --構建向量索引 COMMIT; --設置多個索引 BEGIN; CREATE TABLE t1 ( f1 int PRIMARY KEY, f2 float4[] NOT NULL CHECK(array_ndims(f2) = 1 AND array_length(f2, 1) = 4), f3 float4[] NOT NULL CHECK(array_ndims(f3) = 1 AND array_length(f3, 1) = 4) ); CALL set_table_property( 't1', 'proxima_vectors', '{"f2":{"algorithm":"Graph", "distance_method":"InnerProduct", "builder_params":{"min_flush_proxima_row_count" : 1000, "min_compaction_proxima_row_count" : 1000, "max_total_size_to_merge_mb" : 2000}}, "f3":{"algorithm":"Graph", "distance_method":"InnerProduct", "builder_params":{"min_flush_proxima_row_count" : 1000, "min_compaction_proxima_row_count" : 1000, "max_total_size_to_merge_mb" : 2000}}}'); COMMIT;
先導入數據、再創建向量索引:適用于離線分析場景。
說明Hologres從V2.1.17版本起支持Serverless Computing能力,針對大數據量向量離線導入、大數據量向量查詢等場景,使用Serverless Computing執行該類任務可以直接使用額外的Serverless資源,避免使用實例自身資源,無需為實例預留額外的計算資源,顯著提升實例穩定性、減少OOM概率,且僅需為任務單獨付費。Serverless Computing詳情請參見Serverless Computing概述,Serverless Computing使用方法請參見Serverless Computing使用指南。
--設置單個索引 BEGIN; CREATE TABLE feature_tb ( id bigint, feature_col float4[] CHECK(array_ndims(feature_col) = 1 AND array_length(feature_col, 1) = <value>) --定義向量 ); COMMIT; -- (可選)推薦使用Serverless Computing執行大數據量離線導入和ETL作業 SET hg_computing_resource = 'serverless'; -- 導入數據 INSERT INTO feature_tb ...; VACUUM feature_tb; -- 構建向量索引 CALL set_table_property( 'feature_tb', 'proxima_vectors', '{"<feature_col>":{"algorithm":"Graph", "distance_method":"<value>", "builder_params":{"min_flush_proxima_row_count" : 1000, "min_compaction_proxima_row_count" : 1000, "max_total_size_to_merge_mb" : 2000}}}'); -- 重置配置,保證非必要的SQL不會使用serverless資源。 RESET hg_computing_resource;
參數說明如下。
分類
參數
描述
示例
向量基本屬性
feature_col
向量列名稱。
feature。
array_ndims
向量的維度,僅支持一維向量。
構建一維且長度為4的向量示例如下。
feature float4[] check(array_ndims(feature) = 1 and array_length(feature, 1) = 4)
array_length
向量的長度,最大不超過1000000。
向量索引
proxima_vectors
代表構建向量索引,其中:
algorithm:用于指定構建向量索引的算法,目前僅支持
Graph
。distance_method:用于定義構建向量索引使用的距離計算方法,目前支持三種距離計算函數:
(推薦使用)SquaredEuclidean:平方歐式距離, 查詢效率最高。適合查詢時使用
pm_approx_squared_euclidean_distance
。Euclidean:開方的歐式距離,僅適合查詢時使用
pm_approx_euclidean_distance
,如果使用其他距離函數會利用不上索引。InnerProduct(避免使用):內積距離,會在底層轉換為開方的歐式距離的計算,所以構建索引和查詢索引都會多一層計算開銷,比較低效,盡量避免使用,除非業務有強需求。僅適合查詢時使用
pm_approx_inner_product_distance
。
builder_params:控制索引構建的參數,是一個JSON格式的字符串,包含以下參數。
min_flush_proxima_row_count:數據寫入到磁盤時建索引的最少行數,建議值為1000。
min_compaction_proxima_row_count:數據在磁盤做合并時建索引的最小行數,建議值為1000。
max_total_size_to_merge_mb: 數據在磁盤做合并時的最大文件大小,單位MB,建議值為2000。
proxima_builder_thread_count:控制寫入時build向量索引的線程數,默認值為4,一般場景無需修改。
說明索引需要在一定的場景下使用才能發揮更好的作用。
使用平方歐式距離查詢,構建對應的向量索引示例如下。
call set_table_property( 'feature_tb', 'proxima_vectors', '{"feature":{"algorithm":"Graph", "distance_method":"SquaredEuclidean", "builder_params":{"min_flush_proxima_row_count" : 1000, "min_compaction_proxima_row_count" : 1000, "max_total_size_to_merge_mb" : 2000}}}');
向量導入。
可以通過離線或者實時的方式將數據導入至向量表,詳情請參見數據同步概述,可以根據業務需求選擇合適的同步方式。但需要注意的是,在批量導入后,需要執行VACUUM和Analyze命令以提升查詢效率。
VACUUM會讓后端的文件compaction成更大的文件,對查詢更高效。 但是VACUUM需要耗費一定的CPU資源,表的數據量越大,執行VACUUM的時間越久,當VACUUM還在執行中時,請耐心等待執行結果。
VACUUM <tablename>;
Analyze是收集統計信息,用于優化器QO(Query Optimizer)生成較優的執行計劃,提高查詢性能。
analyze <tablename>;
向量查詢。
Hologres支持精確和近似向量查詢,其中以
pm_
開頭的UDF都為精準查詢,以pm_approx_
開頭的UDF都為近似查詢。只有以pm_approx_開頭的非精確查詢才能命中向量索引,對于構建向量索引的場景,更建議使用近似查詢,查詢效率會更高。只有單表查詢時才能命中向量索引,優先推薦單表向量查詢,避免join操作。近似查詢(使用向量索引)
非精確查詢可以命中向量索引,更適用于掃描數據量大,要求執行效率更高的場景,默認召回精度99%以上。使用向量索引,只需要在對應的距離計算函數前加上
approx_
前綴,對應的距離計算函數如下:說明平方歐式距離、歐式距離的非精確查詢,只支持
order by distance asc
場景下命中向量索引,不支持倒序。內積距離的非精確查詢,只支持
order by distance desc
場景下命中向量索引,不支持正序。
float4 pm_approx_squared_euclidean_distance(float4[], float4[]) float4 pm_approx_euclidean_distance(float4[], float4[]) float4 pm_approx_inner_product_distance(float4[], float4[])
同時查詢時的函數需要和建表時的
proxima_vector
參數中的distance_method
一一對應,使用示例如下。示例使用如下的方式查詢Top N,且近似查詢中的第二個參數必須是常量值。說明索引查詢是有損查詢,會有一定的精度損失,默認召回精度一般在99%以上。
-- 計算平方歐式距離的TOPK,此時建表里面的proxima_vector參數的distance_method需要為SquaredEuclidean select pm_approx_squared_euclidean_distance(feature, '{0.1,0.2,0.3,0.4}') as distance from feature_tb order by distance asc limit 10 ; -- 計算歐式距離的TOPK,此時建表里面的proxima_vector參數的distance_method需要為Euclidean select pm_approx_euclidean_distance(feature, '{0.1,0.2,0.3,0.4}') as distance from feature_tb order by distance asc limit 10 ; -- 計算內積距離的TOPK,此時建表里面的proxima_vector參數的distance_method需要為InnerProduct select pm_approx_inner_product_distance(feature, '{0.1,0.2,0.3,0.4}') as distance from feature_tb order by distance desc limit 10 ;
精確查詢(不使用向量索引)
精確查詢更加適用于SQL掃描數據量少,且對召回率要求高的場景。歐式距離、平方歐式距離、內積距離三種距離計算方式分別對應以下三種距離計算函數。
float4 pm_squared_euclidean_distance(float4[], float4[]) float4 pm_euclidean_distance(float4[], float4[]) float4 pm_inner_product_distance(float4[], float4[])
如果召回和目標向量距離最近的TOP K個鄰居,可以使用以下SQL進行查詢。
說明示例中是進行精確召回計算的SQL。執行過程為:掃描feature列的所有向量進行距離計算,最后將計算結果排序,取前10條輸出。這種SQL適合數據量小,對召回率要求特別高的場景。
-- 召回平方歐式距離最近的10個鄰居 select pm_squared_euclidean_distance(feature, '{0.1,0.2,0.3,0.4}') as distance from feature_tb order by distance asc limit 10 ; -- 召回歐式距離最近的10個鄰居 select pm_euclidean_distance(feature, '{0.1,0.2,0.3,0.4}') as distance from feature_tb order by distance asc limit 10 ; -- 召回內積距離最大的10個鄰居 select pm_inner_product_distance(feature, '{0.1,0.2,0.3,0.4}') as distance from feature_tb order by distance desc limit 10 ;
完整使用示例
對4維x10萬
的向量表,使用Proxima索引召回平方歐式距離最近的40條數據示例如下。
創建向量表。
CREATE EXTENSION proxima; BEGIN; -- 新建一個shard_count = 4 的table group CALL HG_CREATE_TABLE_GROUP ('test_tg_shard_4', 4); CREATE TABLE feature_tb ( id bigint, feature float4[] CHECK (array_ndims(feature) = 1 AND array_length(feature, 1) = 4) ); CALL set_table_property ('feature_tb', 'table_group', 'test_tg_shard_4'); CALL set_table_property ('feature_tb', 'proxima_vectors', '{"feature":{"algorithm":"Graph","distance_method":"SquaredEuclidean","builder_params": {"min_flush_proxima_row_count" : 1000, "min_compaction_proxima_row_count" : 1000, "max_total_size_to_merge_mb" : 2000}}}'); COMMIT;
數據導入。
-- (可選)推薦使用Serverless Computing執行大數據量離線導入和ETL作業 SET hg_computing_resource = 'serverless'; INSERT INTO feature_tb SELECT i, ARRAY[random(), random(), random(), random()]::float4[] FROM generate_series(1, 100000) i; ANALYZE feature_tb; VACUUM feature_tb; -- 重置配置,保證非必要的SQL不會使用serverless資源。 RESET hg_computing_resource;
查詢。
-- (可選)使用Serverless Computing執行大數據量向量查詢作業 SET hg_computing_resource = 'serverless'; SELECT pm_approx_squared_euclidean_distance (feature, '{0.1,0.2,0.3,0.4}') AS distance FROM feature_tb ORDER BY distance LIMIT 40; -- 重置配置,保證非必要的SQL不會使用serverless資源。 RESET hg_computing_resource;
性能調優
設置向量索引的場景
數據量較小,比如幾萬條數據情況下,建議不設置索引直接計算即可。 或者實例資源較多的情況下但查詢的數據量較少,也可以直接計算。 當直接計算滿足不了需求(如延遲、吞吐等要求)時,可以考慮使用Proxima索引, 原因如下。
Proxima本身是有損索引,不保證結果的準確性,即計算出來的距離可能是有偏差的。
Proxima索引有可能導致召回的條數不足,如
limit 1000
情況下,只返回了500條。Proxima索引使用有一定難度。
設置合適的Shard Count
Shard Count越多, 實際構建Proxima索引的文件就越多, 查詢吞吐就越差。所以在實際使用中,根據實例資源建議設置合理的Shard Count,一般可以將Shard Count設置為Worker的數量,例如64 Core的實例建議設置Shard Count為4。同時如果想減少單條查詢的延時,可以減小Shard Count,但是這會降低寫入性能。
-- 創建向量表,并且放于shard_count = 4 的table group中 BEGIN; CALL HG_CREATE_TABLE_GROUP ('test_tg_shard_4', 4); BEGIN; CREATE TABLE proxima_test ( id bigint NOT NULL, vectors float4[] CHECK (array_ndims(vectors) = 1 AND array_length(vectors, 1) = 128), PRIMARY KEY (id) ); CALL set_table_property ('proxima_test', 'proxima_vectors', '{"vectors":{"algorithm":"Graph","distance_method":"SquaredEuclidean","builder_params":{}, "searcher_init_params":{}}}'); CALL set_table_property ('proxima_test', 'table_group', 'test_tg_shard_4); COMMIT;
(推薦)不帶過濾條件的查詢場景
在有
where
過濾條件的情況下,會影響索引使用,性能可能更差,所以推薦不帶過濾條件的查詢場景。對于不帶過濾條件的向量檢索,最極致的狀態就是一個Shard上只有一個向量索引文件,這樣查詢就能直接落在一個Shard上。因此對于不帶過濾條件的查詢場景通常建表如下。
BEGIN; CREATE TABLE feature_tb ( uuid text, feature float4[] NOT NULL CHECK (array_ndims(feature) = 1 AND array_length(feature, 1) = N) --定義向量 ); CALL set_table_property ('feature_tb', 'shard_count', '?'); --指定shard count,根據業務情況合理設置,若有可以不設置 CALL set_table_property ('feature_tb', 'proxima_vectors', '{"feature":{"algorithm":"Graph","distance_method":"InnerProduct"}'); --構建向量索引 END;
帶過濾條件的查詢場景
對于帶過濾條件的向量檢索,情況細分為如下常見的過濾場景。
查詢場景1:字符串列為過濾條件
示例查詢如下,常見的場景為在某個組織內查找對應的向量數據,例如查找班級內的人臉數據。
select pm_xx_distance(feature, '{1,2,3,4}') as d from feature_tb where uuid = 'x' order by d limit 10;
建議進行如下優化。
將uuid設置為Distribution Key,這樣相同的過濾數據會保存在同一個Shard,查詢時一次查詢只會落到一個Shard上。
將uuid設置為表的Clustering Key,數據將會在文件內根據Clustering Key排序。
查詢場景2:時間字段為過濾條件
示例查詢如下,一般是根據時間字段過濾出對應的向量數據。建議將時間字段time_field設置為表的segment_key,可以快速的定位到數據所在的文件。
select pm_xx_distance(feature, '{1,2,3,4}') as d from feature_tb where time_field between '2020-08-30 00:00:00' and '2020-08-30 12:00:00' order by d limit 10;
因此對于帶過濾條件的向量檢索而言,其建表語句通常如下。
BEGIN; CREATE TABLE feature_tb ( time_field timestamptz NOT NULL, uuid text, feature float4[] NOT NULL CHECK (array_ndims(feature) = 1 AND array_length(feature, 1) = N) ); CALL set_table_property ('feature_tb', 'distribution_key', 'uuid'); CALL set_table_property ('feature_tb', 'segment_key', 'time_field'); CALL set_table_property ('feature_tb', 'clustering_key', 'uuid'); CALL set_table_property ('feature_tb', 'proxima_vectors', '{"feature":{"algorithm":"Graph","distance_method":"InnerProduct"}}'); COMMIT; -- 如果沒有按照時間過濾的話,則time_field相關的索引可以刪除。
常見問題
報錯
ERROR: function pm_approx_inner_product_distance(real[], unknown) does not exist
。原因:通常是因為未在數據庫中執行
create extension proxima;
語句來初始化Proxima插件。解決方法:執行
create extension proxima;
語句初始化Proxima插件。報錯
Writing column: feature with array size: 5 violates fixed size list (4) constraint declared in schema
。原因:由于寫入到特征向量列的數據維度與表中定義的維度數不一致,導致出現該報錯。
解決方法:可以排查下是否有臟數據。
報錯
The size of two arrays must be the same in DistanceFunction, size of left array: 4, size of right array:
。原因:由于
pm_xx_distance(left, right)
中,left的維度與right的維度不一致所致。調整
pm_xx_distance(left, right)
中,left的維度與right的維度一致。實時寫入報錯
BackPressure Exceed Reject Limit ctxId: XXXXXXXX, tableId: YY, shardId: ZZ
。原因:實時寫入作業遇到了瓶頸,產生了反壓的異常,說明寫入作業開銷大,寫入慢,通常是由于min_flush_proxima_row_count較小,而實時寫入速度較大,造成寫入作業實時構建索引開銷大,阻塞了實時寫入。
調整min_flush_proxima_row_count為更大值。
如何通過Java寫入向量數據?
通過Java寫入向量數據的示例如下。
private static void insertIntoVector(Connection conn) throws Exception { try (PreparedStatement stmt = conn.prepareStatement("insert into feature_tb values(?,?);")) { for (int i = 0; i < 100; ++i) { stmt.setInt(1, i); Float[] featureVector = {0.1f,0.2f,0.3f,0.4f}; Array array = conn.createArrayOf("FLOAT4", featureVector); stmt.setArray(2, array); stmt.execute(); } } }
如何通過執行計劃檢查是否利用上Proxima索引?
如果執行計劃中存在
Proxima filter: xxxx
表明使用了索引,如下圖所示;如果沒有,則索引沒有使用上,一般是建表語句與查詢語句不匹配。
距離函數說明
Hologres支持的三種向量距離評估函數如下:
不開方的歐式距離(SquaredEuclidean),計算公式如下。
開方的歐氏距離(Euclidean),計算公式如下。
內積距離(InnerProduct),計算公式如下。
如果您選用歐式距離進行向量計算,不開方的歐式距離與開方的歐式距離相比,可以少一個開方的計算,并且計算出的Top K記錄一致。因此,不開方的歐式距離性能更好,在滿足功能需求的情況下,一般建議您使用不開方的歐式距離。