HGraph索引使用指南

Hologres中的向量计算功能可以应用于相似度搜索、图像检索、场景识别等多种场景。通过灵活应用向量计算,可以提升数据处理和分析的效果,并实现更精准的搜索和推荐功能。本文为您介绍在Hologres中使用向量计算的方法及完整示例。

注意事项

  • Hologres V4.0版本起支持HGraph向量检索算法。

  • 仅列存、行列共存表支持创建向量索引,行存表不支持。

  • 创建了向量索引的表如果涉及删表、重建表操作,如Insert Overwrite等,暂不建议开启回收站功能,回收站中的表仍存在部分内存占用。

  • 创建向量索引后,索引文件将在数据导入后的Compaction过程中构建。

  • 内存表(Mem Table)中的数据没有向量索引,在执行向量检索请求时,该部分数据会暴力计算。

  • 建议使用Serverless Computing资源执行数据的批量导入,Serverless资源将在数据导入时同步完成Compaction及向量索引构建,参见使用Serverless Computing执行读写任务、使用Serverless Computing执行Compaction任务。

  • 如不使用Serverless资源,建议在批量导入数据或修改索引后,手动执行如下命令触发Compaction,参见Compaction(Beta)。

    SELECT hologres.hg_full_compact_table('<schema_name>.<table_name>', 'max_file_size_mb=4096');
  • 支持使用Serverless Computing资源执行向量检索的查询。

管理向量索引

创建索引

语法:建表时创建向量索引。

说明:向量在Hologres中通过float4数组表示,向量维度通过一维数组的长度表示,即下文的array_length。

CREATE TABLE <table_name> (
    <vector_column_name> float4[] CHECK (array_ndims(<vector_column_name>) = 1 AND array_length(<vector_column_name>, 1) = <dim>)
)
WITH (
    vectors = '{
    "<vector_column_name>": {
        "algorithm": "<algorithm>",
        "distance_method": "<distance_method>",
        "builder_params": {
            "<builder_parameters_name>": <value>
            [, ...]
        }
        [, "proxima_builder_thread_count": <value>]
    }
    [ , "<vector_column_name_2>": { ... } ]
  }'
);

参数说明:

参数

说明

table_name

目标表名。

vector_column_name

目标向量列名。

dim

目标列的向量维度。

向量索引参数vectors的取值有如下要求:

  • 仅支持JSON格式字符串,顶层仅支持一个vector_column_name键,用于指定需构建向量索引向量列名。

  • vector_column_name键的值为JSON对象,用于配置向量索引参数,支持如下键。

说明

algorithm

向量索引算法。必填,仅支持HGraph。

distance_method

向量距离计算方法。必填。支持如下取值:

  • Euclidean:欧氏距离,只支持正排,即ORDER BY distance ASC。

  • InnerProduct:内积距离,只支持倒排,即ORDER BY distance DESC。

  • Cosine:余弦距离,只支持倒排,即ORDER BY distance DESC。

说明:向量检索使用的距离计算函数需要和向量索引使用的距离计算方法对应,且需要满足对应的排序要求,否则无法使用向量索引。

builder_params

向量索引构建参数。仅支持JSON格式字符串,参数说明见下文。

  • max_degree

  • ef_construction

  • base_quantization_type

  • use_reorder

  • precise_quantization_type

  • precise_io_type

向量索引构建参数builder_params支持如下参数:

参数

说明

max_degree

在索引构建过程中,每个顶点将尝试与其最近的max_degree个顶点建立连接。非必填,默认为64。值越大,每个顶点的搜索范围越大,搜索效率越高,但图构建和存储的成本也越高,一般不建议超过96。

ef_construction

用于控制索引构建过程中的搜索深度。非必填,默认为400。值越大,在索引构建过程中被视为顶点近邻向量的候选者越多,索引精度越高,但索引构建的时间消耗和计算复杂度也相应增加,一般不建议超过600。

base_quantization_type

HGraph低精度索引的量化方法。必填。支持如下方法:

  • sq8

  • sq8_uniform

  • fp16

  • fp32

  • rabitq

use_reorder

是否使用HGraph高精度索引。非必填,默认为FALSE。

precise_quantization_type

HGraph高精度索引的量化方法。非必填,默认为fp32,不建议修改。支持如下方法,建议选择比base_quantization_type更高精度的量化方法。

  • sq8

  • sq8_uniform

  • fp16

  • fp32

precise_io_type

HGraph高精度+低精度混合索引的存储介质。非必填,仅use_reorderTRUE时生效,默认为 block_memory_io。支持如下取值:

  • block_memory_io:低精度索引、高精度索引全部存储在内存。

  • reader_io:低精度索引存储在内存,高精度索引存储在磁盘。

修改索引

语法:

ALTER TABLE <table_name>
SET (
    vectors = '{
    "<vector_column_name>": {
        "algorithm": "<algorithm>",
        "distance_method": "<distance_method>",
        "builder_params": {
            "<builder_parameters_name>": <value>
            [, ...]
        }
        [, "proxima_builder_thread_count": <value>]
    }
  }'
);

删除索引

-- 删除表中全部列的向量索引
ALTER TABLE <table_name>
SET (
    vectors = '{}'
);

-- 如果表中有col1、col2两列均构建向量索引,需删除col2列的索引,则通过ALTER TABLE语句仅保留col1列的索引即可
ALTER TABLE <table_name>
SET (
    vectors = '{
    "col1": { ... }
  }'
);

查看索引

Hologres提供hologres.hg_table_properties系统表,可查看已创建的向量索引。

SELECT
    *
FROM
    hologres.hg_table_properties
WHERE 
    table_name = '<table_name>'
    AND property_key = 'vectors';

使用向量索引进行向量检索

向量距离计算函数

Hologres的向量检索支持近似检索和精确检索,仅近似检索函数可使用已构建的向量索引进行加速查询(函数需要和向量索引的distance_method距离计算方法对应),精确检索函数无法使用向量索引。

说明:向量距离计算函数不支持全部常量入参。

函数

检索类型

入参

返回值

说明

approx_euclidean_distance

近似检索

float4[], float4[]

float4

欧氏距离近似检索函数。

approx_inner_product_distance

近似检索

float4[], float4[]

float4

内积距离近似检索函数。

approx_cosine_distance

近似检索

float4[], float4[]

float4

余弦距离近似检索函数。

euclidean_distance

精确检索

float4[], float4[]

float4

欧氏距离精确检索函数。

inner_product_distance

精确检索

float4[], float4[]

float4

内积距离精确检索函数。

cosine_distance

精确检索

float4[], float4[]

float4

余弦距离精确检索函数。

向量索引使用验证

可通过执行计划查看SQL是否使用了向量索引,若其中出现“Vector Filter”,说明已成功使用,参见EXPLAINEXPLAIN ANALYZE。

  • 示例SQL:

    SELECT
        id,
        approx_euclidean_distance (feature, '{0.1,0.2,0.3,0.4}') AS distance
    FROM
        feature_tb
    ORDER BY
        distance
    LIMIT 40;
  • 执行计划:

    Limit  (cost=0.00..182.75 rows=40 width=12)
      ->  Sort  (cost=0.00..182.75 rows=160 width=12)
            Sort Key: (VectorDistanceRef)
            ->  Gather  (cost=0.00..181.95 rows=160 width=12)
                  ->  Limit  (cost=0.00..181.94 rows=160 width=12)
                        ->  Sort  (cost=0.00..181.94 rows=40000 width=12)
                              Sort Key: (VectorDistanceRef)
                              ->  Local Gather  (cost=0.00..91.53 rows=40000 width=12)
                                    ->  Limit  (cost=0.00..91.53 rows=40000 width=12)
                                          ->  Sort  (cost=0.00..91.53 rows=40000 width=12)
                                                Sort Key: (VectorDistanceRef)
                                                ->  Project  (cost=0.00..1.12 rows=40000 width=12)
                                                      ->  Index Scan using Clustering_index on feature_tb  (cost=0.00..1.00 rows=40000 width=8)
                                                            Vector Filter: VectorCond => KNN: '40'::bigint distance_method: approx_euclidean_distance search_params: {NULL} args: {feature'{0.100000001,0.200000003,0.300000012,0.400000006}'::real[]}
    Query Queue: init_warehouse.default_queue
    Optimizer: HQO version 4.0.0

使用示例

  • 建表。

    -- 新建一个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)
    )
    WITH (
        table_group = 'test_tg_shard_4',
        vectors = '{
        "feature": {
            "algorithm": "HGraph",
            "distance_method": "Cosine",
            "builder_params": {
                "base_quantization_type": "rabitq",
                "max_degree": 64,
                "ef_construction": 400,
                "precise_quantization_type": "fp32",
                "use_reorder": true,
                "max_total_size_to_merge_mb" : 4096
            }
        }
        }'
    );
  • 数据导入。

    -- (可选)推荐使用Serverless Computing执行大数据量离线导入和ETL作业,并在导入时同步完成Compaction与索引构建
    SET hg_computing_resource = 'serverless';
    SET hg_serverless_computing_run_compaction_before_commit_bulk_load = on;
    
    INSERT INTO feature_tb SELECT i, array[random(), random(), random(), random()]::float4[] FROM generate_series(1, 100000) i;
    
    -- 重置配置,保证非必要的SQL不会使用serverless资源。
    RESET hg_computing_resource;
  • 向量近似检索。

    -- 计算欧氏距离的Top 40
    SELECT
        id,
        approx_cosine_distance (feature, '{0.1,0.2,0.3,0.4}') AS distance
    FROM
        feature_tb
    ORDER BY
        distance ASC
    LIMIT 40;
  • 向量精确检索。

    -- 精确检索不使用向量索引,因此距离计算函数无需与向量索引的distance_method相同
    SELECT
        id,
        cosine_distance (feature, '{0.1,0.2,0.3,0.4}') AS distance
    FROM
        feature_tb
    ORDER BY
        distance DESC
    LIMIT 40;

性能调优

合理使用向量索引

当数据量较小(比如几万条),或实例计算资源较多情况下,建议不设置向量索引,直接暴力计算。当直接计算无法满足延迟、吞吐等需求时,再使用向量索引,原因如下:

  • 向量索引是有损索引,结果准确率(召回率)无法达到100%。

  • 向量索引可能出现召回条数不足的情况,如LIMIT 1000却只返回500条。

当选择使用向量索引时,配置建议如下(以单表、单列、768维度向量为例):

  • 延时敏感:建议选择纯内存索引,索引量化方法建议使用sq8_uniformrabitq,单Shard建议数据量不超过500万行。

  • 延时不敏感或大数据量:建议选择内存+磁盘混合索引,索引量化方法建议使用rabitq,单Shard建议数据量不超过3000~5000万行。

  • 说明:当需对多列设置向量索引时,单Shard建议数据量需要等比缩小。同时,向量维度大小也会影响该建议值。

使用示例:

-- 混合索引完整例子
CREATE TABLE feature_tb (
    id bigint,
  	feature float4[] CHECK(array_ndims(feature) = 1 AND array_length(feature, 1) = 4)
)
WITH (
    table_group = 'test_tg_shard_4',
    vectors = '{
    "feature": {
        "algorithm": "HGraph",
        "distance_method": "Cosine",
        "builder_params": {
            "base_quantization_type": "rabitq",
            "max_degree": 64,
            "ef_construction": 400,
            "precise_quantization_type": "fp32",
            "precise_io_type": "reader_io",
            "use_reorder": true,
            "max_total_size_to_merge_mb" : 4096
        }
    }
    }'
);


-- 全内存索引完整例子
CREATE TABLE feature_tb (
    id bigint,
  	feature float4[] CHECK(array_ndims(feature) = 1 AND array_length(feature, 1) = 4)
)
WITH (
    table_group = 'test_tg_shard_4',
    vectors = '{
    "feature": {
        "algorithm": "HGraph",
        "distance_method": "Cosine",
        "builder_params": {
            "base_quantization_type": "sq8_uniform",
            "max_degree": 64,
            "ef_construction": 400,
            "precise_quantization_type": "fp32",
            "use_reorder": true,
            "max_total_size_to_merge_mb" : 4096
        }
    }
    }'
);

提升召回率

本节以 VectorDBBench 数据集为例,说明如何提升召回率。

一般情况下影响召回率的因素有多个,如下索引参数配置下,系统的默认召回率一般可以达到95%以上:

  • base_quantization_type 为 rabitq、sq8_uniform。

  • precise_quantization_type 为 fp32。

  • max_degree 为 64。

  • ef_construction 为 400。

  • hg_vector_ef_search 为系统默认 80。

如需进一步提升召回率至99%以上,可以在保持其余参数不变的情况,设置 hg_vector_ef_search 为 400。但召回率的提升会使得查询延迟和计算资源使用率相应增加。

如需进一步提升召回率至 99.5% ~ 99.7%,可进一步调整 max_degree、ef_construction、hg_vector_ef_search 三个值,查询延迟、查询资源消耗、索引构建时间、索引构建资源消耗均会相应增加,如:

  • max_degree = 96。

  • ef_construction = 500 或 600。

  • hg_vector_ef_search = 500 或 600。

设置合适的Shard Count

Shard Count越多, 实际构建Proxima索引的文件就越多, 查询吞吐就越差。所以在实际使用中,根据实例资源建议设置合理的Shard Count,一般可以将Shard Count设置为Worker的数量,例如64core的实例建议设置Shard Count = 4。同时如果想减少单条查询的延时,可以减小Shard Count,但是这会降低写入性能。

-- 创建向量表,并且放于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)
)
WITH (
    table_group = 'test_tg_shard_4',
    vectors = '{
    "feature": {
        "algorithm": "HGraph",
        "distance_method": "Cosine",
        "builder_params": {
            "base_quantization_type": "sq8_uniform",
            "max_degree": 64,
            "ef_construction": 400,
            "precise_quantization_type": "fp32",
            "use_reorder": true,
            "max_total_size_to_merge_mb" : 4096
        }
    }
    }'
);

向量+标量混合查询场景

对于带过滤条件的向量检索,情况细分为几种常见的过滤场景,分别如下:

查询场景1:某个字符串列为过滤条件

示例查询如下,常见的场景为在某个组织内查找对应的向量数据,例如查找班级内的人脸数据。

SELECT(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 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;

因此对于带任何过滤条件的向量检索而言,其建表语句通常如下:

-- 说明:如果没有按照时间过滤的话,则time_field相关的索引可以删除。
CREATE TABLE feature_tb (
    time_field timestamptz NOT NULL,
    uuid text NOT NULL,
    feature float4[] CHECK(array_ndims(feature) = 1 AND array_length(feature, 1) = 4)
)
WITH (
    distribution_key = 'uuid',
    segment_key = 'time_field',
    clustering_key = 'uuid',
    vectors = '{
    "feature": {
        "algorithm": "HGraph",
        "distance_method": "Cosine",
        "builder_params": {
            "base_quantization_type": "sq8_uniform",
            "max_degree": 64,
            "ef_construction": 400,
            "precise_quantization_type": "fp32",
            "use_reorder": true,
            "max_total_size_to_merge_mb" : 4096
        }
    }
    }'
);

常见问题

  1. 报错“Writting column: feature with array size: 5 violates fixed size list (4) constraint declared in schema”

    原因:由于写入到特征向量列的数据维度与表中定义的维度数不一致导致,可以排查下是否有胀数据。

  2. 报错“The size of two array must be the same in DistanceFunction, size of left array: 4, size of right array: x”

    原因:这是由于xx_distance(left, right)里面,left的维度与right的维度不一致所致。

  3. 通过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();
            }
        }
    }