Beaver 是由日志易自主研发、安全可控的搜索引擎,由 Master、Broker 和 Datanode 三部分组成,已广泛应用于存储和分析大型分布式系统生成的日志。Beaver 拥有大量与性能相关的配置项,由于手动配置费时费力,并且有时需要修改相关配置以适配特定环境,所以自动调整配置参数优化性能是当前迫切需要解决的问题。
目前业界有许多自动调参的项目和算法实现,例如 CMU 开源的关系型数据库自动调参工具 OtterTune[1]、PingCAP 仿作的 TiKV 自动调参工具[2]等,都为我们提供了大量的论文以及开源算法代码。
1、 OtterTune
数据库有很多参数,比如 MySQL 有几百个参数,Oracle 有上千个参数。这些参数控制着数据库的方方面面,很大程度上影响了如缓存容量和检查点频次等数据库性能。
对于不同的硬件配置,不同的工作负载,对应的最优参数文件都是不同的,这些复杂性令数据库调优变得更加困难。DBA(Database Administrator,即数据库管理员)不能简单地重复使用之前调好的参数文件,他们需要花大量时间根据经验来调优数据库的参数,而公司则需要花大价钱来雇佣资深 DBA。
为解决上述问题,卡内基梅隆大学数据库小组的教授、学生和研究人员开发了一个数据库自动调参工具 OtterTune,它能利用机器学习对数据库的参数文件进行自动化调优,利用已有的数据训练机器学习模型,进而实现自动化地推荐最优参数。它能很好地帮助 DBA 进行数据库调优,将 DBA 从复杂繁琐的调参工作中解放出来。
OtterTune 的目的是为了帮助 DBA,让数据库部署和调优更加容易,用机器学习代替人工来完成数据库调参这个冗繁但又很重要的工作,让技术人员甚至不需要专业知识也能顺利完成。
OtterTune 分为客户端和服务端,目标数据库是用户需要调优参数的数据库:
客户端接收到推荐的配置文件后,配置到目标数据库上,并测量其性能。以上步骤可重复进行,直到用户对其推荐的配置文件满意为止。
AutoTikv 是一个用于对 TiKV 数据库进行自动调优的工具。它是根据 SIGMOD 2017 年发表的一篇论文所设计[3],能够使用机器学习模型对数据库参数进行自动调优。
AutoTiKV 吸取了 OtterTune 的设计理念,并简化了相关结构。
设计的调优过程如下:
图一:整个过程会循环跑 200 个 round(用户可自定义),或者定义为直到结果收敛为止。
AutoTikv 使用了和 OtterTune 一样的高斯过程回归(Gaussian Process Regression,以下简称 GP)来推荐新的 knob,它是基于高斯分布的一种非参数模型。
在还没有利用机器学习模型对参数文件的效果进行预测的时候,OtterTune 使用的是随机采样的方式来收集初始数据。
当有足够的数据 (X,Y) 时,OtterTune 训练机器学习模型进行回归,即估计出函数 f:X→Y,表示对于参数文件 X,用 f(X) 来估计数据库延迟 Y 的值。如此,问题则变为寻找合适的 X,是 f(X) 的值尽量小。这样在 f 上面做梯度下降即可找出合适的 X[4]。
如图二所示,横坐标是两个参数:缓存大小和日志文件大小,纵坐标是数据库延迟(越低越好)。
图二:OtterTune 高斯过程回归模型
OtterTune 用高斯过程回归模型估计出了 f,即给定这两个参数值,估计出对应的数据库延迟。接着用梯度下降找到最合适的参数值,使延迟尽可能低。
但 GP 本身其实只能估计样本的分布,为了得到最终的预测值,我们需要把它应用到贝叶斯优化(Bayesian Optimization)中[5]。
采集函数(Acquisition Function)的特性是在寻找新的推荐值的时候,能够进行平衡探索(Exploration)和利用(Exploitation)。
在推荐的过程中,需要平衡上述两种指标。Exploitation 过多会导致结果陷入局部最优值(即重复推荐目前已知最好的点,但可能还有更好的点没被发现),而 Exploration 过多又会导致搜索效率太低(即一直在探索新区域,而没有对当前比较好的区域进行深入尝试)。而平衡二者的核心思想,是当数据足够多时,利用现有的数据推荐;当缺少数据时,在点最少的区域进行探索,探索最未知的区域能够提供最大的信息量。
贝叶斯优化的第二步就可以帮我们实现这一思想。前面提到, GP 可以帮我们估计 X 的均值 m(X) 和标准差 s(X),其中均值 m(X) 可以作为 Exploitation 的表征值,而标准差 s(X) 可以作为 Exploration 的表征值[6],这样就可以用贝叶斯优化方法来求解了。
使用置信区间上界(Upper Confidence Bound)作为采集函数。假设我们需要找 X 诗 Y 值尽可能大,则 U(X) = m(X) + k*s(X),其中 k > 0 是可调的系数,我们只要找 X 使 U(X) 尽可能大即可。
注意:其中系数 k 影响着探索和利用的比例,即 k 越大,越鼓励探索新的区域。
在具体实现中,一开始随机生成若干个 candidate knobs,然后用上述模型计算出它们的 U(X),找出 U(X) 最大的那一个作为本次推荐的结果。
目前所有开源的自动调参工具实现原理基本上都是通过机器学习算法推荐配置参数,应用至数据库或者其他引擎上,在不同的工作负载模式下,不断收集 metric 信息,丰富训练模型,直至推荐出最优的配置,以此替代频繁的手动修改配置工作。
由调研可以发现,OtterTune 是通用模型框架,在业界许多场景都能应用,不仅能调优数据库的参数,还能够调优操作系统内核的参数,即只要能获取指标信息,大部分软件都可以用此模型进行调优。
同时可以借鉴 AutoTiKV 的测试代码,将目标 DB 替换为 Beaver_datanode,通过修改不同的配置,测试 baimi 数据集,收集 search 的性能数据,经过模型训练后不断推荐最优配置。
baimi 数据集,即 Apache 访问日志,总日志行数 7078124,日志文件原始大小 2374265761 Byte,测试 Beaver 和 ES 的搜索性能对比中用到的数据集。
AutoTiKV 代码分析:
1.pipeline.py
自动调参脚本入口,定义执行 round 数,自动推荐参数配置,修改配置文件并重启相关 DB,收集 metric 数据训练算法模型,以文件形式持久化保存对象。
2.settings.py
脚本参数配置,需要测试的 knobs、metrics 及 workload,数据库连接配置。需要优化的 metric(仅支持优化一项目标 metric)、ansible 和 deploy 目录。
3.controller.py
knob 配置和 metric 获取相关函数,每一个需要修改的参数都需要在 knob_set 中定义,声明参数类型和取值范围,修改配置文件和重启数据库函数等。另外包括一些工作负载相关的 workload 函数。
4.datamodel.py
初始化数据设置,存放数据模型,每次测试的配置参数和获取到的指标数据都会存放在此模型中。
5.gpmodel.py
调用高斯过程回归类算法,传递并训练数据模型,根据算法推荐返回最佳配置参数,用于下轮测试。
注意:前十轮为随机生成 knob。
6.gpclass.py
即高斯过程回归算法。
7.showres.py
展示过往测试结果,将持久化保存的对象文件反序列化,调用 datamodel.py 函数中 GPDataSet 类输出测试结果。
本着不重复造轮子的原则,本次测试决定使用 AutoTiKV 的算法代码,并修改其中关于数据库的代码,使其适用于 Beaver_datanode。
首先,TiKV 数据库使用的配置文件是 yaml 格式,而 Beaver 使用的是 flags 参数的形式(–max_concurrency_tasks_per_search=4),代码中使用的 ruamel.yaml 库文件并不适用于 Beaver。因此,对 controller.py 中 set_tikvyml 函数进行修改,以“=”为分隔符,读取旧配置文件并将参数以键值对形式写入字典中,对需要修改的配置项进行替换,最后把修改过后的配置参数写入到新配置文件中。
需要修改的配置参数应在 settings.py 中提前声明,更新 target_knob_set 列表中的参数,新增 wl_metrics 中 avgsearch 列表,并设置期望的 metric。在 controller.py 中补充参数的类型和取值范围,配置好 knob_set 和 metric_set。修改 metric 数据获取函数,其中 read_search_latency() 函数是基于已经索引好的 baimi 数据集,测试某个场景的 search 性能。
参考了 Esrally 的性能压测代码[7],调用 Beaver_broker 的 API 接口,通过传递 pb 格式的搜索语句,来获取不同场景下的 latnecy。本次自动调参测试中,使用的搜索场景是从数据集中获取 apache.resp_len 字段的平均值,可以根据自己的实际环境自定义场景。因为每次得到的性能数据可能受到各种因素影响,或产生较大误差,为了降低误差值,搜索请求预热 20 次,压测 100 次,并计算前 90th 的平均值作为最终的 metric 数据。
settings.py 中需要修改的配置:
# beaver集群的broker地址和端口,主要用来测试搜索性能
beaver_broker_ip="172.21.16.16"
beaver_broker_port="50061"
# 测试搜索性能需要的索引
index_forsearch="ops-http_baimi-20210507"
# pb类型搜索语句,求apache.resp_len字段的平均值
pb_forsearch='search_info {query {type: kQueryMatchAll}fetch_source {fetch: true}size {value: 0} aggregations { aggs { type: kAggAvg name: "av(apache.resp_len)" body { field: "apache.resp_len__l__" } } } query_time_range {time_range {min: 0 max: 1620374828405}}}'
# 不同工作负载模式下相关的指标,可以随意命名,workload
wl_metrics={
"avgsearch": ["search_latency","compaction_mem","compaction_cpu"],
}
# workload to be load
loadtype = "avgsearch"
# workload to be run
wltype = "avgsearch"
# 需要优化的目标指标
target_metric_name="search_latency"
# 待调整的配置项
target_knob_set=['--enable_query_cache', # 启用query cache
'--max_concurrency_tasks_per_search', # 每个Search允许同时执行的数目
'--max_per_search_ram', # 单个Search最大占用的内存
'--max_per_sub_search_ram', # 单个SubSearch最大占用的内存
'--block_ids_per_batch'] # 每个SubSearch的Block数目
复制代码
以下是 knob 和 metric 在 controller.py 中的声明样板:
knob_set=
{"--max_concurrency_tasks_per_search": # 配置项
{
"changebyyml": True, # True表示通过修改配置文件来调节
"set_func": None, # 若changebyyml==False,则在此指定修改参数的函数名(在controller.py中定义函数),一般是不需要重启beaver的配置
"minval": 0, # if type==int, indicate min possible value
"maxval": 0, # if type==int, indicate max possible value
"enumval": [4, 6, 8], # if type==enum, list all valid values
"type": "enum", # int / enum
"default": 0 # default value
}
}
metric_set=
{"search_latency":
{
"read_func": read_search_latency, # 声明查看该指标的函数(函数也定义在controller.py里)
"lessisbetter": 1, # whether less value of this metric is better(1: yes)
"calc": "ins", # ins表示该参数的值就是benchmark之后查看的结果。inc表示该参数是incremental的,需要把benchmark之后和之前的值相减作为结果。
},
}
复制代码
Beaver 重启操作比较直接,使用 os.popen 管道命令直接在服务器上执行 kill 命令后更新配置文件重新启动,具体操作在 controller.py 文件的 restart_beaver_datanode() 函数中。在本次测试中,脚本直接运行在 Beaver 所运行的服务器中,首先需要在 settings.py 文件中声明 Beaver_datanode 的启动命令和各项配置文件路径,指定配置文件临时存放路径。使用 “ps -ef|grep beaver_datanode” 即可查看 Beaver_datanode 的启动命令。
具体配置示例如下:
autotestdir="/tmp/auto_beaver_datanode"
beaver_datanode_file="/opt/rizhiyi/parcels/beaver_datanode-3.7.0.0/bin/beaver_datanode"
gflagsfile="/run/rizhiyi_manager_agent/process/2002-beaver_datanode/config/beaver_datanode.gflags"
config_path="/run/rizhiyi_manager_agent/process/2002-beaver_datanode/config/beaver_datanode.pb"
log_dir="/data/rizhiyi/logs/beaver_datanode"
复制代码
我们选择了如下几个 metrics 作为优化指标。
注意:knobs 和 metrics 均在 contorller.py 文件中定义。
安装 Python 3.6
$ tar -xf Python-3.6.12.tgz -C /tmp
# 编译前检查系统openssl版本是否为1.0.2k以上版本,并安装所需依赖包,否则编译Python时会报错。
$ yum install -y openssl* bzip2* xz-devel zlib-devel libffi-devel gcc gcc-c++ sqlite sqlite-devel
$ cd /tmp/Python-3.6.12/
$ ./configure
# 编译无报错后可以执行make安装,默认会安装到/usr/local/lib/python3.6
# 使用该命令参数可以安装到指定位置:./configure --prefix=/xxx
$ make && make install
复制代码
安装虚拟环境,切换到 Python 3.6 环境,下载自动化调参工具,并使用 pip 安装相关依赖包
$ python3.6 -m venv autoenv
$ source autoenv/bin/activate
(autoenv)$ git clone https://github.com/woxiang-H/auto-beaver.git
(autoenv)$ cd auto-beaver
(autoenv)$ pip install -r requirements.txt
复制代码
按照之前具体实现中提到的需要修改的配置,修改 auto-beaver 下 settings.py 文件。
调整好 settings.py 之后,执行 python pipeline.py
等待结果收敛,查看推荐配置
################## data ##################
------------------------------previous:------------------------------
rowlabels, finish_time, knobs, metrics
1 , 2021-05-11 16:41:02 , [0. 1. 0. 0. 1.] , [124.71111111 1.4 31.8 ]
2 , 2021-05-11 16:43:41 , [0. 1. 0. 0. 0.] , [127.38888889 1.4 31.6 ]
3 , 2021-05-11 16:46:10 , [1. 1. 0. 0. 0.] , [127.54444444 1.4 33.3 ]
4 , 2021-05-11 16:48:48 , [0. 1. 0. 0. 2.] , [126.81111111 1.4 31.6 ]
5 , 2021-05-11 16:51:27 , [1. 1. 0. 0. 2.] , [126.23333333 1.4 31.5 ]
6 , 2021-05-11 16:54:06 , [0. 1. 0. 0. 2.] , [124.63333333 1.4 31.8 ]
7 , 2021-05-11 16:56:35 , [1. 2. 0. 0. 1.] , [125.13333333 1.4 32.6 ]
8 , 2021-05-11 16:59:14 , [1. 0. 0. 0. 1.] , [125.5 1.4 31.8]
9 , 2021-05-11 17:01:53 , [1. 0. 0. 0. 2.] , [131.18888889 1.4 31.9 ]
10 , 2021-05-11 17:04:31 , [0. 2. 0. 0. 1.] , [124.22222222 1.4 31.5 ]
11 , 2021-05-11 17:07:53 , [0. 2. 0. 0. 2.] , [128.48888889 1.4 31.4 ]
12 , 2021-05-11 17:11:14 , [0. 2. 0. 0. 1.] , [123.74444444 1.4 31.9 ]
13 , 2021-05-11 17:14:35 , [0. 1. 0. 0. 1.] , [123.34444444 1.4 31.1 ]
14 , 2021-05-11 17:17:56 , [0. 2. 0. 0. 1.] , [127.08888889 1.4 31.5 ]
15 , 2021-05-11 17:21:17 , [0. 0. 0. 0. 1.] , [124.66666667 1.4 31.8 ]
16 , 2021-05-11 17:24:38 , [0. 1. 0. 0. 1.] , [123.34444444 1.4 31.2 ]
17 , 2021-05-11 17:27:59 , [0. 2. 0. 0. 1.] , [126.15555556 1.4 31.2 ]
18 , 2021-05-11 17:31:21 , [0. 1. 0. 0. 1.] , [126.26666667 1.4 31.6 ]
19 , 2021-05-11 17:34:41 , [0. 0. 0. 0. 1.] , [123.67777778 1.4 31.3 ]
20 , 2021-05-11 17:38:04 , [0. 1. 0. 0. 1.] , [127.13333333 1.4 31.5 ]
21 , 2021-05-11 17:41:15 , [0. 0. 0. 0. 1.] , [126.35555556 1.4 33.1 ]
22 , 2021-05-11 17:44:36 , [0. 1. 0. 0. 1.] , [124.56666667 1.4 31.5 ]
23 , 2021-05-11 17:47:57 , [0. 0. 0. 0. 1.] , [124.36666667 1.4 31.2 ]
24 , 2021-05-11 17:51:19 , [0. 1. 0. 0. 1.] , [129.04444444 1.4 31.4 ]
25 , 2021-05-11 17:54:39 , [0. 0. 0. 0. 1.] , [122.94444444 1.4 31.2 ]
26 , 2021-05-11 17:58:01 , [0. 1. 0. 0. 1.] , [125.03333333 1.4 31.1 ]
27 , 2021-05-11 18:01:22 , [0. 0. 0. 0. 1.] , [128.91111111 1.4 31.7 ]
28 , 2021-05-11 18:04:42 , [0. 1. 0. 0. 1.] , [125.67777778 1.4 31.4 ]
29 , 2021-05-11 18:08:04 , [0. 0. 0. 0. 1.] , [124.78888889 1.4 31.2 ]
30 , 2021-05-11 18:11:15 , [1. 1. 0. 0. 1.] , [125.37777778 1.4 32.7 ]
31 , 2021-05-11 18:14:36 , [0. 0. 0. 0. 1.] , [126.62222222 1.4 31.3 ]
32 , 2021-05-11 18:17:57 , [0. 2. 0. 0. 1.] , [123.52222222 1.4 31.4 ]
33 , 2021-05-11 18:21:19 , [0. 0. 0. 0. 1.] , [125.54444444 1.4 31.3 ]
34 , 2021-05-11 18:24:31 , [0. 2. 0. 0. 1.] , [126.58888889 1.4 32.9 ]
35 , 2021-05-11 18:27:52 , [0. 0. 0. 0. 1.] , [123.44444444 1.4 31.4 ]
36 , 2021-05-11 18:31:12 , [0. 2. 0. 0. 1.] , [125.65555556 1.4 31.4 ]
37 , 2021-05-11 18:34:34 , [0. 0. 0. 0. 1.] , [125.83333333 1.4 31.5 ]
38 , 2021-05-11 18:37:45 , [1. 1. 0. 0. 1.] , [128.74444444 1.4 32.8 ]
39 , 2021-05-11 18:41:06 , [0. 0. 0. 0. 1.] , [123.74444444 1.4 31.4 ]
40 , 2021-05-11 18:44:28 , [1. 2. 0. 0. 1.] , [127.13333333 1.4 31.7 ]
41 , 2021-05-11 18:47:49 , [0. 0. 0. 0. 1.] , [127.9 1.4 31.4]
42 , 2021-05-11 18:51:00 , [0. 1. 0. 0. 1.] , [124.71111111 1.4 32.9 ]
43 , 2021-05-11 18:54:21 , [0. 0. 0. 0. 1.] , [125.51111111 1.4 31.1 ]
44 , 2021-05-11 18:57:42 , [0. 1. 0. 0. 1.] , [123.42222222 1.4 31.4 ]
45 , 2021-05-11 19:01:03 , [0. 0. 0. 0. 1.] , [124.04444444 1.4 31.4 ]
46 , 2021-05-11 19:04:24 , [0. 1. 0. 0. 1.] , [124.28888889 1.4 31.2 ]
47 , 2021-05-11 19:07:35 , [0. 0. 0. 0. 1.] , [125.75555556 1.4 32.7 ]
48 , 2021-05-11 19:10:57 , [0. 1. 0. 0. 1.] , [127.17777778 1.4 31.3 ]
49 , 2021-05-11 19:14:17 , [0. 0. 0. 0. 1.] , [123.52222222 1.4 31.5 ]
50 , 2021-05-11 19:17:38 , [0. 1. 0. 0. 1.] , [125.33333333 1.4 31.5 ]
51 , 2021-05-11 19:20:58 , [0. 0. 0. 0. 1.] , [126.82222222 1.4 31.3 ]
52 , 2021-05-11 19:24:19 , [0. 1. 0. 0. 1.] , [124.15555556 1.4 31.4 ]
53 , 2021-05-11 19:27:40 , [0. 0. 0. 0. 1.] , [126.34444444 1.4 31.5 ]
54 , 2021-05-11 19:31:01 , [0. 1. 0. 0. 1.] , [124.68888889 1.4 31.4 ]
55 , 2021-05-11 19:34:23 , [0. 0. 0. 0. 0.] , [125.5 1.4 31.4]
56 , 2021-05-11 19:37:44 , [0. 1. 0. 0. 1.] , [125.61111111 1.4 31.4 ]
57 , 2021-05-11 19:40:55 , [0. 0. 0. 0. 0.] , [125.16666667 1.4 32.8 ]
58 , 2021-05-11 19:44:16 , [0. 1. 0. 0. 1.] , [125.06666667 1.4 31.5 ]
59 , 2021-05-11 19:47:37 , [0. 0. 0. 0. 0.] , [125.54444444 1.4 31.2 ]
60 , 2021-05-11 19:50:58 , [0. 1. 0. 0. 1.] , [125.55555556 1.4 31.4 ]
61 , 2021-05-11 19:54:20 , [0. 0. 0. 0. 0.] , [128.92222222 1.4 31.5 ]
62 , 2021-05-11 19:57:31 , [0. 1. 0. 0. 1.] , [125.9 1.4 32.9]
------------------------------new:------------------------------
knobs: [[0. 0. 0. 0. 0.]]
metrics: [[124.1 1.4 31.5]]
rowlabels: [1]
timestamp: 2021-05-11 20:00:52
------------------------------TARGET:------------------------------
knob: ['--enable_query_cache' '--max_concurrency_tasks_per_search'
'--max_per_search_ram' '--max_per_sub_search_ram' '--block_ids_per_batch']
metric: search_latency
metric_lessisbetter: 1
------------------------------------------------------------
num of knobs == 5
knobs: ['--enable_query_cache' '--max_concurrency_tasks_per_search'
'--max_per_search_ram' '--max_per_sub_search_ram' '--block_ids_per_batch']
num of metrics == 3
metrics: ['search_latency' 'compaction_mem' 'compaction_cpu']
------------------------------------------------------------
复制代码
可以看到最佳推荐配置为[0. 0. 0. 0. 0.]和[0. 1. 0. 0. 1.](此数字含义为 list 中的下标索引),具体配置参数如下。
set_beaver_datanode_gflags:: --enable_query_cache false
set_beaver_datanode_gflags:: --max_concurrency_tasks_per_search 4
set_beaver_datanode_gflags:: --max_per_search_ram 198m
set_beaver_datanode_gflags:: --max_per_sub_search_ram 99m
set_beaver_datanode_gflags:: --block_ids_per_batch 16
复制代码
或者
set_beaver_datanode_gflags:: --enable_query_cache false
set_beaver_datanode_gflags:: --max_concurrency_tasks_per_search 6
set_beaver_datanode_gflags:: --max_per_search_ram 198m
set_beaver_datanode_gflags:: --max_per_sub_search_ram 99m
set_beaver_datanode_gflags:: --block_ids_per_batch 18
复制代码
结果显示,适当提高 search 并发数,或提高 SubSearch 的 block 数据优化 search 性能。
通过修改相关代码,目前自动调参工具能正常运行,但依然存在不足。本次测试方案利用事前存储好的索引 baimi 数据集,仅测试影响 search 性能的参数,因此可修改的配置项也相对较少。虽然舍弃了 AutoTiKV 的 workload,但代码依然保留此功能,待后续有针对 Beaver 的工作负载方案之后,再添加相关 workload。此外 Beaver_datanode 的重启方式也并不优雅,有待提升。
后续可优化:
原文链接:https://www.infoq.cn/article/2Mea47tw9CBs8BbX6BRU