ESrally单机向量检索性能测试全流程

455 阅读12分钟

ESrally单机向量检索性能测试全流程

测试方案的尝试

准备测试 ES 的向量检索性能,Vespa 方案由于下载依赖库存在网络问题无法执行成功,终止;开源工具 ann-benchamrk 是一个用于评估近似最近邻(ANN)搜索库的性能测试工具,这个本是最佳选择,但是也由于需要 pip 安装几十个依赖和 docker 构建十分麻烦,且详细的教程介绍太少,最后还是选择 esrally 进行性能测试,这个也是 ES 官方博客所使用的。

测试环境

uname -r
# 5.4.6-1.el7.elrepo.x86_64 内核版本
lscpu
# Intel(R) Xeon(R) Gold 6130T CPU @ 2.10GHz 处理器型号
# Architecture:          x86_64 架构
cat /etc/redhat-release
# CentOS Linux release 7.4.1708 (Core) 操作系统版本
python3 --version 
# Python 3.10.11

在线安装 esrally

pip3 install esrally

Httpx 版本冲突

由于之前安装了其他 ollama-python 0.1.2 导致一些依赖版本冲突:

ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts. ollama-python 0.1.2 requires httpx<0.27.0,>=0.26.0, but you have httpx 0.27.0 which is incompatible. ollama-python 0.1.2 requires responses<0.25.0,>=0.24.1, but you have responses 0.18.0 which is incompatible.

处理办法:pip 3 uninstall ollama-python

安装完查看一下 esrally 版本(不能直接使用 esrally):

cd /root/python3/Python-3.10/bin
# ./esrally --version
esrally 2.10.0

./esrally --help

返回内容如下(有省略):

usage: esrally [-h] [--version] {race,list,delete,info,create-track,compare,build,download,install,start,stop,add} ...

    ____        ____
   / __ \____ _/ / /_  __
  / /_/ / __ `/ / / / / /
 / _, _/ /_/ / / / /_/ /
/_/ |_|\__,_/_/_/\__, /
                /____/

 You Know, for Benchmarking Elasticsearch.

options:
  -h, --help            show this help message and exit
  --version             show program's version number and exit

subcommands:
  {race,list,delete,info,create-track,compare,build,download,install,start,stop,add}

Dense vector track 介绍

rally-tracks/dense_vector at master · elastic/rally-tracks (github.com)

本赛道(track)用于对密集向量场的索引和搜索进行基准测试。

该数据集包含 1,000 万个矢量,维度为 96。该数据集基于 Yandex DEEP 1 B 图像数据集,可在此处下载:big-ann-benchmarks.com/。 数据集由名为 learn.350M.fbin 的 "样本数据 "文件的前 1000 万个向量创建。

使用下面这个命令生成 JSON 格式数据集:

python3 _tools/parse.py data/learn.350M.fbin > documents.json

文档示例

{
  "vector": [0.21529805660247803, -0.06119159981608391, 0.08770883828401566, 0.08731604367494583, -0.03312725946307182, -0.06861377507448196, 0.011172166094183922, 0.08099681884050369, 0.06873716413974762, -0.10662394016981125, -0.06803347170352936, -0.22509372234344482, 0.04775683954358101, -0.11963146924972534, -0.13713325560092926, 0.040520284324884415, 0.03633395954966545, -0.06001321226358414, 0.05640476569533348, -0.1323852241039276, 0.09493865817785263, 0.08581436425447464, 0.11651603877544403, -0.007319801487028599, 0.07001037895679474, -0.20735269784927368, 0.09725717455148697, -0.09026120603084564, 0.027974626049399376, 0.07966648787260056, -0.00902935303747654, -0.25620758533477783, -0.03905373811721802, 0.1739693433046341, -0.0062398542650043964, 0.1722472906112671, -0.005885206162929535, -0.15015317499637604, -0.13207486271858215, -0.05693387985229492, 0.1926010102033615, -0.03730269894003868, 0.06276655197143555, 0.06388836354017258, -0.07929308712482452, -0.04800641909241676, -0.04506685957312584, -0.025829171761870384, 0.11739484965801239, 0.04018357768654823, 0.03557595983147621, -0.14328059554100037, -0.009096059016883373, 0.01991777867078781, 0.19544541835784912, 0.16432076692581177, -0.17786552011966705, -0.011906086467206478, -0.022930240258574486, 0.0394594669342041, -0.1750987023115158, -0.07123833894729614, 0.03142083063721657, -0.03363385424017906, 0.042252954095602036, -0.12258686870336533, 0.0053991954773664474, -0.0580497682094574, -0.13083989918231964, 0.01648520492017269, -0.15029865503311157, 0.04681768640875816, -0.22521495819091797, -0.028500616550445557, 0.09996795654296875, 0.04748646169900894, -0.12404435873031616, 0.11154870688915253, -0.018465351313352585, -0.009930790401995182, -0.04939839243888855, -0.1522141546010971, 0.21582986414432526, -0.05126684159040451, -0.020097043365240097, -0.05058222636580467, 0.0635807067155838, 0.01816011220216751, 0.0961170494556427, 0.06972803920507431, 0.13445426523685455, -0.015163707546889782, -0.0299700740724802, 0.03558037802577019, 0.10351981967687607, -0.025422528386116028]
}

Parameters参数含义

Rally 0.8.0+ 使用 --track-params 接收下列参数:

  • bulk_size (默认值: 5000)
  • bulk_indexing_clients (默认: 1): Number of clients that issue bulk indexing requests.
  • index_settings: A list of index settings. Index settings defined elsewhere (e.g. number_of_replicas) need to be overridden explicitly.
  • max_num_segments (默认: 1): The number of segments to target when doing a force merge.
  • number_of_replicas (默认: 0)
  • post_ingest_sleep (默认: false): Whether to pause after ingest and prior to subsequent operations.
  • post_ingest_sleep_duration (默认: 30): Sleep duration in seconds.
  • vector_index_type (默认: "hnsw"): The index kind for storing the vectors.

性能测试准备

下载数据集

Esrally 好像没有单独下载数据的命令,直接开始测试后会自行下载所需数据。

esrally race --track=dense-vector

报错 clone 失败

esrally.exceptions.SupplyError: Could not clone from [https://github.com/elastic/rally-tracks] to [/root/.rally/benchmarks/tracks/default]

猜测是 git 时网络波动,之前也用过 esrally 测试其他项目, 解决办法:重试。

如果还不行就单独手动 clone 项目到指定的位置并改名:

cd /root/.rally/benchmarks/tracks/
git clone https://github.com/elastic/rally-tracks.git
mv rally-tracks default

可能出现 The requested URL returned error: 403,需要重试或自行下载 zip 包上传。

报错非 git 项目

esrally.exceptions.SystemSetupError: [/root/.rally/benchmarks/tracks/default] must be a git repository.

Please run:
git -C /root/.rally/benchmarks/tracks/default init

解决办法:

cd /root/.rally/benchmarks/tracks/default
git init

报错 git 版本过低

Your git version is [['git version 1.8.3.1']] but Rally requires at least git 1.9. Please update git.

提示我升级 git 到 1.9,但是我们最终是离线运行,资源已经自行下载过,理论上是不需要使用 git 的。修改源码 vim ./utils/git.py 注释掉版本报错行即可。

指定离线参数无效

./esrally  race --offline --track=dense-vector --pipeline=benchmark-only

当前报错:(Could not checkout [master]. Do you have uncommitted changes? You may need to specify --offline if running without Internet connection.).

实际上 git 相关的报错非常多,也非常难缠,花费了很多时间来解决这一系列问题。

使用 --offline 只是不会下载一些内容,但是连接git的操作依然会执行,通过 --track-path=/home/deploy/.rally/benchmarks/tracks/default/... 重新指定本地的track则可以绕过git的连接,如果使用 --track=geonames 这个参数,将会一直去连接git找到这个最新的track信息。

需注意的是这样配置之后还是会报错缺少 teams 项目。

解决办法(git 项目只是一小部分,测试数据 7G 多是尚未下载好的):

  1. 下载数据
sh download.sh dense_vector
% Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                Dload  Upload   Total   Spent    Left  Speed
23 7333M   23 1759M    0     0  1125k      0  1:51:09  0:26:39  1:24:30 1120k

mv dense_vector dense-vector

2. 修改配置文件:vim rally.ini

[tracks]
default.url = ~/.rally/benchmarks/tracks/default

3. 测试命令修改 --track=dense-vector 成如下的内容:

--track-path=/home/deploy/.rally/benchmarks/tracks/default/<测试项名称>
  1. 报错找不到 teams 项目,这个也是和 rally-tracks 一样的,自行下载后拷贝到指定位置并改名为 default
git clone https://github.com/elastic/rally-teams.git
cp -r ./rally-teams/ ~/.rally/benchmarks/teams/default
  1. 呜呼,下载工作基本结束了,准备起飞

安装启动本地 ES

我们的目的是测试自己的环境中的 ES 集群,那么这里要使用自行搭建好的 ES 集群。这里先搭建一个单节点的本地服务,供性能测试用。

su elastic
./bin/elasticsearch -d

后台启动 ES 后可以执行如下命令检查是否正常:

curl  http://localhost:9200/_cluster/health?pretty
{
  "cluster_name" : "es-prod",
  "status" : "green",
  "timed_out" : false,
  "number_of_nodes" : 1,
  "number_of_data_nodes" : 1,
  "active_primary_shards" : 0,
  "active_shards" : 0,
  "relocating_shards" : 0,
  "initializing_shards" : 0,
  "unassigned_shards" : 0,
  "delayed_unassigned_shards" : 0,
  "number_of_pending_tasks" : 0,
  "number_of_in_flight_fetch" : 0,
  "task_max_waiting_in_queue_millis" : 0,
  "active_shards_percent_as_number" : 100.0
}

执行测试

./esrally race --track-path=/root/.rally/benchmarks/tracks/default/dense_vector --target-hosts=ESIP:9200 --pipeline=benchmark-only --offline 

解压数据这里耗费的时间不少。

上述冒烟测试,参数未作任何调整,后续正式的测试需要至少调大 ES 集群的内存设置、测试时的分片数(也可以测试单分片的性能)、并发数。

curl  http://localhost:9200/_cat/indices?v
curl -XDELETE  http://localhost:9200/vectors

设置主分片数发现当前测试并不支持,可以参看前面章节允许设置哪些参数。

我们查看一下默认的索引设置是怎么样的:

curl http://localhost:9200/vectors/_mappings?pretty

{
  "vectors" : {
    "mappings" : {
      "dynamic" : "strict",
      "_source" : {
        "enabled" : false
      },
      "properties" : {
        "vector" : {
          "type" : "dense_vector",
          "dims" : 96,
          "index" : true,
          "similarity" : "dot_product",
          "index_options" : {
            "type" : "hnsw",
            "m" : 32,
            "ef_construction" : 100
          }
        }
      }
    }
  }
}

如果确实要修改主分片数,index.json 源码位置位于 /root/.rally/benchmarks/tracks/default/dense_vector

向量索引解释

上述的索引描述的是 Elasticsearch 中的一种索引结构,特别适用于高维向量空间的搜索优化,常用于诸如图像识别、自然语言处理、推荐系统等需要计算向量相似度的场景。

下面是对这段配置的详细解释:

  • "vectors": 这是索引名称或者类型的一个标识,并不是实际的数据类型,而是用户自定义的名称。
  • "mappings": 映射定义了文档字段如何存储和索引。这是 Elasticsearch 中定义数据结构的地方。
  • "dynamic": "strict": 指定了文档映射的动态处理行为。"strict"模式意味着如果文档中有映射中未定义的字段,写入时会抛出错误,确保所有字段都是预定义的。
  • "_source": { "enabled": false }: 表示禁用 _source 字段的存储。默认情况下,Elasticsearch 会存储文档的原始 JSON(即_source 字段),以便在检索时返回完整的文档。这里禁用它可能是为了节省存储空间,尤其是在向量本身足够表示文档内容,或者原始文档存储在其他地方的情况下。
  • "vector": 是一个字段名,表示这个字段是用来存储高维向量数据的。
  • "type": "dense_vector": 指定该字段的数据类型为密集向量,适合存储如机器学习模型生成的固定长度的浮点型向量。
  • "dims": 96: 定义了向量的维度,这里是 96 维。这意味着每个文档在这个字段下都会有一个长度为 96 的浮点数数组。
  • "index": true: 表示该字段需要被索引,这样才能对其进行搜索和过滤操作。
  • "similarity": "dot_product": 设置了相似度计算方法为点积 (dot_product)。在向量空间中,点积常用来衡量两个向量的相似度,值越接近说明两个向量方向越一致,相似度越高。
  • "index_options": 这里配置了针对高维向量的特定索引选项,使用的是 HNSW (Hierarchical Navigable Small World) 算法,这是一种高效的近似最近邻搜索算法,特别适合高维空间。
    • "type": "hnsw": 指定使用 HNSW 算法来构建索引。
    • "m": HNSW 中的一个参数,影响索引的构建质量和搜索效率,通常是一个较小的数值,这里是 32。
    • "ef_construction": 在构建 HNSW 索引时使用的临时邻接链的大小,较大的值可能会提高搜索精度但同时增加索引构建时间,这里是 100。

完整的性能测试命令:

./esrally race --track-path=/root/.rally/benchmarks/tracks/default/dense_vector --target-hosts=localhost:9200 --pipeline=benchmark-only  --offline  --report-file=/home/elastic/report_dv_sd_cd-30g.csv --report-format=csv --kill-running-processes

全部项目最终运行了约 2770 秒。

查看结果

结果会在指定的 csv 报告文件中输出一份,也会直接在屏幕上打印,摘取部分结果如下:

Cumulative indexing time of primary shards,,13.3031,min
Mean Throughput,index-append,4647.58,docs/s
99th percentile latency,index-append,1487.4399612145496,ms
Median Throughput,index-update-concurrent-with-searches,5808.68,docs/s
Mean Throughput,knn-search-100-1000-concurrent-with-indexing,46.24,ops/s
Mean Throughput,knn-search-10-100_multiple_segments,124.47,ops/s
Mean Throughput,knn-search-100-1000_multiple_segments,55.26,ops/s
Median Throughput,knn-search-10-100,144.09,ops/s
Median Throughput,knn-search-100-1000,61.82,ops/s
Mean Throughput,script-score-query,5.03,ops/s

下面是对每个指标含义的解释:

  1. Cumulative indexing time of primary shards: 这个指标表示在测试期间,所有主分片累计花费的索引时间总和。单位为分钟(min),在这里是 13.3031 分钟。这个值可以用来评估数据写入效率。

  2. Mean Throughput, index-append: 表示索引追加操作的平均吞吐量,即平均每秒成功索引的文档数量。这里的值是 4647.58 docs/s,意味着在测试过程中,平均而言每秒可以处理 4647.58 份文档的索引操作。

  3. 99 th percentile latency, index-append: 这是索引追加操作的 99%分位延迟,即 99%的索引请求在该时间内完成。数值为 1487.4399612145496 毫秒 (ms),意味着只有 1%的索引请求耗时超过这个值,是一个衡量延迟稳定性的指标。

  4. Median Throughput, index-update-concurrent-with-searches: 表示在并发执行索引更新与搜索操作时,索引更新操作的中位数吞吐量,即每秒能处理的文档数。这里是 5808.68 docs/s,展示了在有搜索负载同时进行时,索引更新的效率。

  5. Mean Throughput, knn-search-100-1000-concurrent-with-indexing: KNN(K-Nearest Neighbors,最近邻)搜索的平均吞吐量,在并发执行索引和搜索操作时,针对每秒能处理的查询次数(ops/s)。这里是 46.24 ops/s,表明在有索引压力的同时,每秒能完成 46.24 次这样的 KNN 查询。

  6. Mean Throughput, knn-search-10-100_multiple_segments & knn-search-100-1000_multiple_segments: 分别代表在多段(segment)的情况下,KNN 搜索查询的平均吞吐量,针对查询向量数量不同(10-100 和 100-1000)的情况。这两个值(124.47 ops/s 和 55.26 ops/s)说明了在不同查询规模下系统的处理能力。

  7. Median Throughput, knn-search-10-100 & knn-search-100-1000: 类似于上面的指标,但这里是中位数吞吐量,分别对应每秒能处理的查询次数为 144.09 和 61.82 次。中位数通常比平均值更能抵抗异常值影响,提供更稳定的性能表现参考。

  8. Mean Throughput, script-score-query: 脚本评分查询的平均吞吐量,表示每秒能处理的此类查询次数。这里为 5.03 ops/s,脚本评分查询通常较为复杂,涉及脚本计算文档得分,因此吞吐量相对较低。

[!note] 备注 knn-search-10-10010 指的是返回的 Top 结果数,就是 KNN 的 K 是多少,而 100 指的是 num_candidates,这个参数是 HNSW 算法所特有的,它控制着我们要考虑的近邻队列的大小(好奇者请注意:它等同于原论文中的 ef 参数)。

此外,测试使用的命令行参数有很多,可参考致谢中的官方手册,具体的使用后续再研究探讨。

关于离线测试

任何离线测试的都需要在线的操作,前述流程执行之后所需的材料已经在本地。

  • Esrally 的安装位置:Python-3.10/lib/python 3.10/site-packages/esrally
  • Rally 命令位置:Python-3.10/bin
  • Rally 代码及比赛描述等:/root/.rally/benchmarks/tracks/default
  • 例如其中一个数据集的具体位置:/root/.rally/benchmarks/data/dense-vector

那么离线测试时应该至少包括以上内容,拷贝到目标测试环境中。

致谢

本实践遭遇的部分问题解答参考了以下内容:

1. esrally离线实践总结-阿里云开发者

2. ESraslly官方文档track参考手册

3. rally-tracks/dense_vector at master · elastic/rally-tracks

4. ESrally命令行参考手册

5. Simplifying kNN search