向量分析性能测试
本文介绍云原生数据仓库 AnalyticDB PostgreSQL 版向量分析的性能测试。
测试环境
云原生数据仓库 AnalyticDB PostgreSQL 版实例与客户端ECS应处于同一VPC中,以避免网络波动带来的误差。
AnalyticDB PostgreSQL服务端规格
引擎版本 | 高性能版节点规格 | 计算节点数量 | 计算节点存储空间 | 计算节点存储类型 |
v6.6.2.5 | 8C32G | 2 | 1000 GB | ESSD 云盘 PL1 |
客户端ECS规格
CPU | 内存 | 磁盘 |
16 核 | 32 GB | 2 TB |
准备工作
准备测试环境
本地安装3.8及以上版本的Python环境。
下载适配云原生数据仓库 AnalyticDB PostgreSQL 版的ann-benchmark测试工具到本地。下载链接,请参见adbpg_ann_benchmark。
执行如下语句,安装测试工具依赖。
pip install -r requirements.txt
安装20版本以上的Docker。具体操作,请参见Docker官方安装指南。
执行以下语句,构建测试镜像。
python install.py --proc 4 --algorithm adbpg
准备测试数据集
下载所需的数据集,将数据集放置于ann-benchmarks项目的data目录下。
数据集 | 维度 | 样本数 | 度量函数 | dataset参数 | 下载地址 |
GIST | 960 | 1,000,000 | L2相似度 | gist-960-euclidean | |
SIFT-10M | 128 | 10,000,000 | L2相似度 | sift-128-euclidean | |
SIFT-100M | 128 | 100,000,000 | L2相似度 | sift100m-128-euclidean | |
Deep | 96 | 10,000,000 | 余弦相似度 | deep-image-96-angular | |
Cohere | 768 | 1,000,000 | L2相似度 | cohere-768-euclidean | |
Dbpedia | 1536 | 1,000,000 | 余弦相似度 | dbpedia-openai-1000k-angular |
测试流程
步骤一:配置测试工具连接信息
编辑测试工具中ann_benchmarks/algorithms/adbpg/module.py
文件,根据实际情况填写配置信息:
# AnalyticDB PostgreSQL实例的内网地址。
self._host = 'gp-bp10ofhzg2z****-master.gpdb.rds.aliyuncs.com'
# AnalyticDB PostgreSQL实例的端口号。
self._port = 5432
# AnalyticDB PostgreSQL实例的数据库名称。
self._dbname = '<database_name>'
# AnalyticDB PostgreSQL实例的账号。
self._user = '<user_name>'
# AnalyticDB PostgreSQL实例的账号密码。
self._password = '<your_password>'
步骤二:配置测试参数
根据测试数据集,编辑测试工具中ann_benchmarks/algorithms/adbpg/config.yml
文件。
float:
any:
- base_args: ['@metric']
constructor: ADBPG
disabled: false
docker_tag: ann-benchmarks-adbpg
module: ann_benchmarks.algorithms.adbpg
name: adbpg
run_groups:
nopq_mmap:
arg_groups: [{M: 64, efConstruction: 600, parallel_build: 8, external_storage: 1, pq_enable: 0, pq_segments: 120}]
query_args: [[ {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 1},
{ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 5},
{ef_search: 400, max_scan_points: 3200, pq_amp: 10, parallel: 10},
{ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 15},
{ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 20},
{ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 25},
{ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 30},
{ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 50}]]
arg_groups
:创建索引的相关参数。如何创建向量索引,请参见创建向量索引。
参数名 | 说明 |
M | HNSW索引的M值参数。M越大,构建越慢,构建精度越高。 |
efConstruction | HNSW索引用于控制搜索质量。 |
parallel_build | 构建索引的并行度,一般设置为计算节点的CPU数量。 |
external_storage | 设置缓存索引方式,取值说明:
|
pq_enable | 是否开启PQ,取值说明:
|
pq_segments | PQ切分的segment数量,一般取向量维度 |
query_args
:检索相关参数。
参数名 | 说明 |
ef_search | HNSW索引中控制搜索过程候选最近邻数量。 |
max_scan_points | 控制索引最多检索的样本数。 |
pq_amp | 开启PQ时的检索放大系数,在非PQ时不起作用。 |
parallel | 检索的并发数,仅在Batch模式中生效。 |
在测试过程中,需要对上述参数进行微调,以保证95%的召回率。对于上述的测试数据集,云原生数据仓库 AnalyticDB PostgreSQL 版提供以下配置供参考,可根据相应的数据集选取对应的参数配置。
# for gist 960
float:
any:
- base_args: ['@metric']
constructor: ADBPG
disabled: false
docker_tag: ann-benchmarks-adbpg
module: ann_benchmarks.algorithms.adbpg
name: adbpg
run_groups:
nopq_mmap:
arg_groups: [{M: 64, efConstruction: 600, parallel_build: 8, external_storage: 1, pq_enable: 0, pq_segments: 120}]
query_args: [[ {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 1}, {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 5}, {ef_search: 400, max_scan_points: 3200, pq_amp: 10, parallel: 10}, {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 15}, {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 20}, {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 25}, {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 30}, {ef_search: 100, max_scan_points: 3200, pq_amp: 10, parallel: 50}]]
# for deep 96
float:
any:
- base_args: ['@metric']
constructor: ADBPG
disabled: false
docker_tag: ann-benchmarks-adbpg
module: ann_benchmarks.algorithms.adbpg
name: adbpg
run_groups:
nopq_mmap:
arg_groups: [{M: 64, efConstruction: 600, parallel_build: 8, external_storage: 1, pq_enable: 0, pq_segments: 12}]
query_args: [[ {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 1}, {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 5}, {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 10}, {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 15}, {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 20}, {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 25}, {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 30}, {ef_search: 400, max_scan_points: 1500, pq_amp: 10, parallel: 50}]]
# for cohere 768
float:
any:
- base_args: ['@metric']
constructor: ADBPG
disabled: false
docker_tag: ann-benchmarks-adbpg
module: ann_benchmarks.algorithms.adbpg
name: adbpg
run_groups:
nopq_mmap:
arg_groups: [{M: 64, efConstruction: 600, parallel_build: 8, external_storage: 1, pq_enable: 0, pq_segments: 96}]
query_args: [[ {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 1}, {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 5}, {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 10}, {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 15}, {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 20}, {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 25}, {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 30}, {ef_search: 400, max_scan_points: 600, pq_amp: 10, parallel: 50}]]
# for dbpedia 1536
float:
any:
- base_args: ['@metric']
constructor: ADBPG
disabled: false
docker_tag: ann-benchmarks-adbpg
module: ann_benchmarks.algorithms.adbpg
name: adbpg
run_groups:
nopq_mmap:
arg_groups: [{M: 64, efConstruction: 600, parallel_build: 8, external_storage: 1, pq_enable: 0, pq_segments: 192}]
query_args: [[ {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 1}, {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 5}, {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 10}, {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 15}, {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 20}, {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 25}, {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 30}, {ef_search: 400, max_scan_points: 425, pq_amp: 10, parallel: 50}]]
步骤三:测试检索召回率
完成上述参数配置后,执行以下命令进行召回率测试:
nohup python run.py --algorithm adbpg --dataset <数据集> --runs 1 --timeout 990000
> annbenchmark_deep.log 2>&1 &
dataset:需要替换为具体测试数据集。
等待测试结束后,执行以下命令以查看召回率结果:
python plot.py --dataset <数据集> --recompute
输出结果示例:
0: ADBPG(m=64, ef_construction=600, ef_search=400, max_scan_point=500, pq_amp=10) recall: 0.963 qps: 126.200
1: ADBPG(m=64, ef_construction=600, ef_search=400, max_scan_point=1000, pq_amp=10) recall: 0.992 qps: 122.665
检查召回率是否符合预期,若不符合,需要调节参数并重新执行测试。
步骤四:测试检索性能
在完成召回率调整后,即可进行性能测试,方法与召回率测试类似,但在此环节中,需要打开Batch模式,以检测并发性能:
nohup python run.py --algorithm adbpg --dataset <数据集> --runs 1 --timeout 990000 --
batch > annbenchmark_deep.log 2>&1 &
等待测试运行结束,查看输出文件annbenchmark_deep.log
,可以查看不同并发下的QPS、平均RT及P99 RT表现:
2023-12-20 17:31:39,297 - INFO - query using 25 parallel
worker 0 cost 9.50 s, qps 315.92, mean rt 0.00317, p99 rt 0.00951
2023-12-20 17:31:49,097 - INFO - QPS: 7653.155
2023-12-20 17:31:49,113 - INFO - query using 30 parallel
worker 0 cost 13.87 s, qps 216.36, mean rt 0.00462, p99 rt 0.04298
2023-12-20 17:32:03,260 - INFO - QPS: 6361.819
2023-12-20 17:32:03,281 - INFO - query using 50 parallel
worker 0 cost 20.78 s, qps 144.36, mean rt 0.00693, p99 rt 0.02735
2023-12-20 17:32:24,385 - INFO - QPS: 7107.920
测试结果
下文提供了不同数据集在不同向量数据库配置,不同索引构建模式下的性能表现结果,所有测试的召回率调节为大于或等于95%,测试过程中检索统一取Top10。其中不同索引构建模式说明如下:
索引构建模式 | 说明 | 适用场景 |
PQ + mmap | 采用mmap向量索引的缓存与持久化存储,并且使用PQ量化方式压缩向量编码和加速向量计算。 | 1. 数据量大于100w。 2. 更新删除占比较小。 3. 内存资源不足以完全缓存所有向量。 4. 性能要求中等。 |
noPQ + mmap | 采用mmap做向量索引的缓存与持久化存储,不使用PQ量化压缩向量编码和加速向量计算。 | 1. 更新删除占比较小 。 2. 内存资源足以完全缓存所有向量和索引。 3. 性能要求最好。 |
PQ + shared_buffer | 采用PostgreSQL原生的shared_buffer机制进行向量索引的缓存,并且使用PQ量化方式压缩向量编码和加速向量计算。 | 1. 数据量大于100w。 2. 存在大量的删除更新操作。 3. 内存资源不足以存储所有向量。 4. 性能要求中等。 |
noPQ + shared_buffer | 采用PostgreSQL原生的shared_buffer机制进行向量索引的缓存,不进行PQ量化压缩向量编码和加速向量计算。 | 1. 数据量小于100w。 2. 内存资源足够存储所有向量和索引。 3. 更新删除占比非常多。 4. 性能要求中等。 |
实例规格:8C32G * 2 segment
数据集:GIST L2 (960 * 100w)
索引构建模式:noPQ + mmap
建索引参数:
M: 64
efConstruction: 600
parallel_build: 8
external_storage: 1
pq_enable: 0
搜索参数:
ef_search: 100
max_scan_points: 3200
测试结果:
索引构建时间(s)
查询并发
QPS
mean RT (ms)
P99 RT (ms)
485
1
396
1
2
5
1744
2
3
10
3073
2
4
15
3358
3
10
20
3511
5
15
25
3601
6
21
30
3689
7
25
50
3823
12
36
数据集:Deep IP (96 * 1000w)
索引构建模式:noPQ + mmap
建索引参数:
M: 64
efConstruction: 600
parallel_build: 8
external_storage: 1
pq_enable: 0
搜索参数:
ef_search: 400
max_scan_points: 1500
测试结果:
索引构建时间(s)
查询并发
qps
mean rt (ms)
p99 rt (ms)
1778s
1
878
1
2
5
4344
1
2
10
7950
1
3
15
10114
1
4
20
10629
1
5
25
10858
2
7
30
11093
2
9
50
11354
4
16
数据集:cohere L2 (768 * 100w)
索引构建模式:noPQ + mmap
建索引参数:
M: 64
efConstruction: 600
parallel_build: 8
external_storage: 1
pq_enable: 0
搜索参数:
ef_search: 400
max_scan_points: 600
测试结果:
索引构建时间(s)
查询并发
qps
mean rt (ms)
p99 rt (ms)
465s
1
561
1
2
5
2893
1
2
10
5108
1
3
15
5488
2
5
20
5969
2
8
25
6195
3
12
30
6098
4
19
50
6138
7
39
数据集:Dbpedia IP (1536 * 100w)
索引构建模式:noPQ + mmap
建索引参数:
M: 64
efConstruction: 600
parallel_build: 8
external_storage: 1
pq_enable: 0
搜索参数:
ef_search: 400
max_scan_points: 425
测试结果:
索引构建时间(s)
查询并发
qps
mean rt (ms)
p99 rt (ms)
807s
1
453
1
2
5
1948
1
3
10
2820
2
4
15
2903
4
11
20
2860
6
19
25
2897
7
27
30
2880
9
34
50
2877
16
63