pgvector性能测试(基于HNSW索引)

本文介绍RDS PostgreSQL实例pgvector插件基于HNSW索引的性能测试,使用ann-benchmarks工具进行评估,涵盖了召回率、QPS、索引构建时间等关键指标。

测试环境

RDS PostgreSQL实例与客户端ECS应位于同一VPC和交换机,以避免网络波动带来的误差。

测试实例及工具

说明

RDS PostgreSQL实例规格

  • 大版本:RDS PostgreSQL 16

  • 规格:标准版高可用系列独享型规格,pg.x8.2xlarge.2c(16核 128GB)

  • pgvector插件版本:0.8.0

客户端ECS规格

  • 规格:ecs.c6.xlarge(4核 8GiB)

  • 操作系统:Alibaba Cloud Linux 3

测试工具

ann-benchmarks

重要

ann-benchmarks默认测试单线程性能。如需测试实例的并发性能,请参见pgvector性能测试(基于IVF索引)

准备工作

RDS PostgreSQL实例侧

  1. 创建高权限账号ann_testuser和测试数据库ann_testdb,详情请参见创建账号和数据库

  2. 在测试数据库ann_testdb中安装插件pgvector(即vector),详情请参见管理插件

客户端ECS

  1. 安装Docker,详情请参见安装Docker

  2. 执行如下命令,下载ann-benchmark测试工具。

    cd ~
    git clone https://github.com/erikbern/ann-benchmarks.git
  3. 执行如下命令,使用Conda创建并激活一个虚拟环境ann_test,同时在该环境中安装Python 3.10.6。

    yum install git
    yum install conda
    conda create -n ann_test python=3.10.6
    conda init bash
    source /usr/etc/profile.d/conda.sh 
    conda activate ann_test
  4. 执行如下命令,安装测试ann-benchmark测试工具的依赖。

    cd ~/ann-benchmarks/
    pip install -r requirements.txt

测试流程

重要

测试流程中的所有步骤均在虚拟环境ann_test中进行。如果因超时或其他原因退出,请使用命令conda activate ann_test重新进入该环境。

步骤一:配置测试工具连接信息

编辑测试工具中的~/ann-benchmarks/ann_benchmarks/algorithms/pgvector/module.py文件,增加如下的连接配置,并根据实际情况填写配置信息。

# 设置 PostgreSQL 的连接参数
os.environ['ANN_BENCHMARKS_PG_USER'] = 'ann_testuser'     # RDS PostgreSQL实例的账号
os.environ['ANN_BENCHMARKS_PG_PASSWORD'] = 'testPawword'  # RDS PostgreSQL实例账号的密码
os.environ['ANN_BENCHMARKS_PG_DBNAME'] = 'ann_testdb'     # RDS PostgreSQL实例的数据库名称
os.environ['ANN_BENCHMARKS_PG_HOST'] = 'pgm-****.pg.rds.aliyuncs.com'  # RDS PostgreSQL实例的内网地址
os.environ['ANN_BENCHMARKS_PG_PORT'] = '5432'             # RDS PostgreSQL实例的内网端口号
os.environ['ANN_BENCHMARKS_PG_START_SERVICE'] = 'false'   # 禁用自动启动服务

步骤二:配置ann-benchmarks测试参数

根据实际测试需要,编辑测试工具中的~/ann-benchmarks/ann_benchmarks/algorithms/pgvector/config.yml文件。例如:

float:
  any:
  - base_args: ['@metric']
    constructor: PGVector
    disabled: false
    docker_tag: ann-benchmarks-pgvector
    module: ann_benchmarks.algorithms.pgvector
    name: pgvector
    run_groups:
      M-16(100):
        arg_groups: [{M: 16, efConstruction: 100}]
        args: {}
        query_args: [[10, 20, 40, 80, 120, 200, 400, 800]]
      M-16(200):
        arg_groups: [{M: 16, efConstruction: 200}]
        args: {}
        query_args: [[10, 20, 40, 80, 120, 200, 400, 800]]
      M-24(200):
        arg_groups: [{M: 24, efConstruction: 200}]
        args: {}
        query_args: [[10, 20, 40, 80, 120, 200, 400, 800]]                                                            

本测试分为M-16(100)、M-16(200)和M-24(200)三组。每一组测试使用arg_groups配置创建HNSW索引的相关参数;使用query_args配置检索相关参数。

参数

说明

arg_groups

M

对应HNSW索引中的参数m,表示构建HNSW索引时,每层中每个节点的最大邻近节点数目。

该值越大,图的稠密度越高,通常会导致召回率的提高,同时构建和查询所需的时间也相应增加。

efConstruction

对应HNSW索引中的参数ef_construction,表示构建HNSW索引时,候选集的大小,即搜索在构建过程中保留多少候选节点用于选择最优连接。

该值越大,通常召回率也越高,但构建和查询所需的时间也相应增加。

query_args

ef_search

查询阶段设置,表示搜索过程中维护候选集的大小。

该值越大,通常召回率越高,但查询时间也会相应增加。

步骤三:构建测试docker镜像

  1. (可选)将测试工具中的~/ann-benchmarks/ann_benchmarks/algorithms/pgvector/Dockerfile文件内容改为如下所示,以跳过对工具默认配置的社区版PostgreSQL的测试。

    FROM ann-benchmarks
    USER root
    RUN pip install psycopg[binary] pgvector
  2. 指定参数--algorithm pgvector 来执行install.py,构建测试docker。

    cd ~/ann-benchmarks/
    python install.py --algorithm pgvector
    说明

    您可以执行python install.py --help查看支持配置参数。

步骤四:获取数据集

执行测试脚本时,将自动下载指定的公开测试数据集。

本文以文本相似性检索为例,采用Angular距离类型的数据集nytimes-256-angular进行测试。更多公开的数据集请参见ann-benchmarks

数据集

维度

数据行数

测试向量数

最近邻Top N

距离类型

NYTimes

256

290,000

10,000

100

Angular

说明

如果公开数据集无法满足测试需求,建议将实际的向量数据转换为标准格式HDF5或其他标准格式,以用于测试检索性能。详情请参见附录二:自定义测试数据集

步骤五:进行测试并获取测试结果

  1. 执行如下命令,运行测试脚本进行测试。

    cd ~/ann-benchmarks
    nohup python run.py --dataset nytimes-256-angular -k 10  --algorithm pgvector --runs 1 > ann_benchmark_test.log 2>&1 &
    tail -f ann_benchmark_test.log

    参数

    含义

    --dataset

    指定要测试的数据集。

    --k

    查询SQLLIMIT值,即返回的结果数量。

    --algorithm

    被测试的向量数据库算法,此处指定为pgvector。

    --runs

    测试的运行次数,将从中选取最佳结果集。

    --parallelism

    并发数,默认为1。

  2. 执行如下命令,获取测试结果。

    cd ~/ann-benchmarks
    python plot.py --dataset nytimes-256-angular --recompute

    测试结果如下:

    m

    ef_construction

    ef_search

    召回率

    QPS

    16

    100

    10

    0.630

    1423.985

    20

    0.741

    1131.941

    40

    0.820

    836.017

    80

    0.871

    574.733

    120

    0.894

    440.076

    200

    0.918

    297.267

    400

    0.945

    162.759

    800

    0.969

    84.268

    16

    200

    10

    0.683

    1299.667

    20

    0.781

    1094.968

    40

    0.849

    790.838

    80

    0.895

    533.826

    120

    0.914

    405.975

    200

    0.933

    272.591

    400

    0.956

    148.688

    800

    0.977

    76.555

    24

    200

    10

    0.767

    1182.747

    20

    0.840

    922.770

    40

    0.887

    639.899

    80

    0.920

    411.140

    120

    0.936

    303.323

    200

    0.953

    199.752

    400

    0.973

    105.506

    800

    0.988

    53.904

  3. (可选)执行如下命令,获取详细的测试结果,包括召回率、QPS、响应时间、索引构建时间和索引大小等指标的结果。

    cd ~/ann-benchmarks
    python data_export.py --out res.csv

    例如,可以获取参数mef_construction和索引构建时间之间的关系:

    m

    ef_construction

    build(索引构建时间,单位:s)

    16

    100

    33.35161

    16

    200

    57.66014

    24

    200

    87.22608

测试结论

在构建HNSW索引时:

  • 增加mef_constructionef_search可以提高召回率,但会导致QPS降低。

  • 增加mef_construction同样会提高召回率,同时也会导致QPS降低,并延长索引构建时间。

  • 如果对召回率的要求较高,不建议使用默认的索引参数(m=16、ef_construction=64ef_search=40)。

附录一:RDS PostgreSQL参数、向量参数对索引构建的影响

在不同ann-benchmarks的测试参数下,通过对测试结果的分析,可以确定RDS PostgreSQL参数及向量参数对索引构建的影响。

RDS PostgreSQL参数对索引构建的影响

例如,当ann-benchmarks的测试参数设置为m=16efConstruction=64时,RDS PostgreSQL参数对索引构建的影响如下。

  • maintenance_work_mem

    用于维护操作的最大内存,包括VACUUM、CREATE INDEX等操作,单位为KB。maintenance_work_mem值小于测试数据大小时,增大该值会缩短索引构建时间;然而,当maintenance_work_mem值超过数据大小后,索引构建时间将不再减少。

    pg.x8.2xlarge.2c(16核 128GB)规格的RDS PostgreSQL为例,实例参数max_parallel_maintenance_workers设置为默认值8,nytimes-256-angular数据集数据量约324MB,maintenance_work_mem参数对索引构建的影响:

    maintenance_work_mem

    索引构建时间(s)

    64 MB(65536 KB)

    52.82

    128 MB(131072 KB)

    46.79

    256 MB(262144 KB)

    36.40

    512 MB(524288 KB)

    18.90

    1 GB(1048576 KB)

    19.06

  • max_parallel_maintenance_workers

    设置CREATE INDEX并行工作的最大数量。索引构建时间随max_parallel_maintenance_workers增加而减少。

    pg.x8.2xlarge.2c(16核 128GB)规格的RDS PostgreSQL为例,实例参数maintenance_work_mem设置为默认值2048(即2 GB),nytimes-256-angular数据集数据量约324 MB,max_parallel_maintenance_workers参数对索引构建的影响:

    max_parallel_maintenance_workers

    索引构建时间(s)

    1

    76.00

    2

    51.34

    4

    32.49

    8

    19.66

    12

    14.44

    16

    13.07

    24

    13.15

向量参数对索引构建的影响

例如,当RDS PostgreSQL参数设置为maintenance_work_mem=8 GB(8388608 KB)和max_parallel_maintenance_workers=16时,RDS PostgreSQL参数对索引构建的影响如下。

  • 向量维度

    使用GloVe数据集,行数固定为1183514。ann-benchmarks的测试参数设置为m=16、efConstruction=64ef_search=40时,测试结果如下。根据测试结果可以得出,索引构建时间随着向量维度的增加而相应增加;同时,召回率和QPS均呈下降趋势,查询延迟则有所增加。

    维度

    构建时间(s)

    召回率

    QPS

    p99(ms)

    25

    195.10

    0.99985

    192.94

    7.84

    50

    236.92

    0.99647

    152.36

    9.69

    100

    319.36

    0.97231

    126.89

    11.14

    200

    529.33

    0.93186

    95.05

    15.11

    说明

    p99:将所有查询请求的响应时间按升序排序后,位于第99百分位位置的延迟值。即表示在所有查询请求中,99%的请求响应时间低于此值,也就是说,只有1%的请求响应时间超过这个值。

  • 向量行数

    使用dbpedia-openai-{n}k-angular数据集,向量行数(n)支持范围为1001000。ann-benchmarks的测试参数设置为m=48、efConstruction=256ef_search=200时,测试结果如下。根据测试结果可以得出,索引构建时间随向量行数的增加呈现出非线性增长趋势;同时,召回率和QPS均有所下降,查询延迟也随之增加。

    向量行数

    行数(万)

    构建时间(s)

    召回率

    QPS

    p99(ms)

    100

    10

    54.05s

    0.9993

    171.74

    8.93

    200

    20

    137.23

    0.99901

    146.78

    10.81

    500

    50

    436.68

    0.999

    118.55

    13.94

    1000

    100

    957.26

    0.99879

    101.60

    16.35

附录二:自定义测试数据集

如下数据集制作参考nytimes-256-angular数据格式,仅供参考。

  1. 执行如下命令,制作自定义数据集。

    说明

    本示例中需要安装rds_ai插件,详情请参见AI(rds_ai)

    import h5py
    import numpy as np
    import psycopg2
    import pgvector.psycopg2
    
    # 假设
    conn_info = {
        'host': 'pgm-****.rds.aliyuncs.com',
        'user': 'ann_testuser',
        'password': '****',
        'port': '5432',
        'dbname': 'ann_testdb'
    }
    
    embedding_len = 1024
    distance_top_n = 100
    query_batch_size = 100
    
    try:
        # 连接rds pg数据库
        with psycopg2.connect(**conn_info) as connection:
            pgvector.psycopg2.register_vector(connection)
            with connection.cursor() as cur:
                # 获取向量数据
                cur.execute("select count(1) from test_rag")
                count = cur.fetchone()[0]
    
                train_embeddings = []
                for start in range(0, count, query_batch_size):
                    query = f"SELECT embedding FROM test_rag ORDER BY id OFFSET {start} LIMIT {query_batch_size}"
                    cur.execute(query)
                    res = [embedding[0] for embedding in cur.fetchall()]
                    train_embeddings.extend(res)
                train = np.array(train_embeddings)
    
                # 获取查询数据并计算嵌入
                with open('query.txt', 'r', encoding='utf-8') as file:
                    queries = [query.strip() for query in file]
                test = []
                # 安装rds_ai插件或者使用百炼SDK
                for query in queries:
                    cur.execute(f"SELECT rds_ai.embed('{query.strip()}')::vector(1024)")
                    test.extend([cur.fetchone()[0]])
                test = np.array(test)
    
        # 计算最近Top n距离
        dot_product = np.dot(test, train.T)
        norm_test = np.linalg.norm(test, axis=1, keepdims=True)
        norm_train = np.linalg.norm(train, axis=1, keepdims=True)
        similarity = dot_product / (norm_test * norm_train.T)
        distance_matrix = 1 - similarity
    
        neighbors = np.argsort(distance_matrix, axis=1)[:, :distance_top_n]
        distances = np.take_along_axis(distance_matrix, neighbors, axis=1)
    
        with h5py.File('custom_dataset.hdf5', 'w') as f:
            f.create_dataset('distances', data=distances)
            f.create_dataset('neighbors', data=neighbors)
            f.create_dataset('test', data=test)
            f.create_dataset('train', data=train)
            f.attrs.update({
                "type": "dense",
                "distance": "angular",
                "dimension": embedding_len,
                "point_type": "float"
            })
    
        print("HDF5 文件创建成功,并已添加数据集。")
    
    except (Exception, psycopg2.DatabaseError) as error:
        print(f"Error: {error}")
    
  2. ~/ann-benchmarks/ann_benchmarks/datasets.py文件的DATASETS部分中,增加自定义数据集。

    DATASETS: Dict[str, Callable[[str], None]] = {
      ......,
      "<custom_dataset>": None,
    }
  3. 将自定义数据集上传至~/ann-benchmarks目录下,运行测试脚本run.py时,即可使用。