问题背景
数据库进程运行时,我们希望操作系统的usr利用率尽可能的高,而sys利用率尽可能低。然而,事与愿违,我们发现进程在大部分运行时间内,sys CPU使用率都处于比较高的状态,这会影响usr使用率,进而拖慢数据库的整体运行。
大家在做性能测试时,可能只想到比对修改前后的执行时间,观察是否有性能优化。这种方法会漏掉一些可能存在的优化点。这里,我们通过这次Teledb的性能优化案例,向大家介绍nmon与flamegraph这两款工具,并用它们来定位潜在的性能问题。
测试模型:40个并发的TPCH 22个SQL连续运行
NMON
nmon是linux系统下的一款强大的监控工具。通过nmon,我们可以在参数设定的时间内监控系统的CPU、内存、网络、硬盘等使用情况。同时,通过第三方工具如nmonchart,可以将监控结果输出为可视化程度较高的视图。
通过yum命令,可以安装nmon工具:
yum install nmon
通过nmon命令,可以指定监控时间并生成可以被转化为视图的.nmon结果:
# 每10秒采样一次,一共采样8640次(即监控24小时),生成结果输出至/my/nmon/data/path/
nmon -f -s10 -c8640 -m /my/nmon/data/path/ -p
通过nmonchart工具,可以将nmon生成结果转化为视图
# 参考下载链接,下载后解压
wget http://sourceforge.net/projects/nmon/files/nmonchart40.tar
# nmonchart转化
/path/to/nmonchart /my/nmon/data/path/xxx.nmon /dest/result/path/xxx.html
至此,将最终生成的html文件下载到本地使用浏览器打开即可。
注意:nmonchart生成视图需要使用google在线js
至此,通过nmon我们可以观察到部分时间有大量的usr CPU占用:
我们希望知道具体是哪个TPCH SQL导致的SYS CPU占用高。为此,我们可以写脚本来分别执行SQL,并在完成时通过kill命令提前结束nmon执行(请勿使用kill -9,否则将出现数据损坏问题)。参考脚本可见文章最后。
FlameGraph
perf是 Linux 系统原生提供的性能分析工具, 会返回 CPU 正在执行的函数名以及调用栈。得到perf抓取的数据后,通过flamegraph工具,我们可以获得如下的调用栈视图,进而观察哪个函数哪些调用占用过高导致了性能问题。
安装perf与获取flamegraph:
yum install perf
git clone https://github.com/brendangregg/FlameGraph.git
通过perf命令生成运行堆栈统计:
# -a: 全系统抓取(需要root权限)
# -o:输出文件路径
# -F:每秒抓取次数,这里是每秒抓20次
# sleep: 持续抓取时间,这里是10800秒
perf record -g -a -o /path/to/output.data -F 20 sleep 10800
得到生成的.data文件后,通过flamegraph目录下的脚本转化成.svg文件用浏览器打开:
perf script -i /path/to/output.data > /path/to/output.unfold
/mypath/FlameGraph/stackcollapse-perf.pl /path/to/output.unfold > /path/to/output.folded
/mypath/FlameGraph/flamegraph.pl /path/to/output.folded > /path/to/output.svg
在我们知道哪个SQL导致的SYS CPU使用率高后,可以通过linux自带的perf工具和flamegraph工具将其转化为可视的火焰图,进而观察到底是哪个函数大量调用系统函数导致的SYS占用高问题。如上图所示,我们发现send_tuplestore_to_rda_frame函数导致的系统函数调用占了整个堆栈调用的50%以上。这与我们的预期不符。
那么显而易见,问题就是出在了RDA算子上,我们通过优化RDA算子的代码,避免了过多的写文件动作,将其占用降低至1%左右,问题解决:
参考脚本
这个脚本监控了正在执行的psql,通过不断查询psql是否执行完成,结束nmon和perf的监控。需要root权限执行。
#!/bin/bash
# 运行nmon并获取其进程的pid
nmon_pid=$(nmon -f -s3 -c2400 -m /my/path/nmon/data_single_sql/ -p)
# 运行perf并获取进程pid
perf record -g -a -o /my/path/nmon/tpch_run_script/perf/single_sql/perf_sql_${current_sql}_$(date +"%m%d_%H%M").data -F 20 sleep 10800 &
perf_pid=$(ps -ef | grep perf | grep tpch_run_script | grep single_sql | awk '{print $2}')
echo "monitoring psql now."
echo "once psql is done, kill nmon process: ${nmon_pid} and perf process: ${perf_pid}"
# 循环,每10秒看一下psql是否执行结束
while true; do
# 获取当前psql进程数量
psql_process_count=$(ps -ef | grep zhangzh | grep psql | wc -l)
echo "get psql_process_count: $psql_process_count"
# 如果psql进程数量少于2,杀死nmon进程与perf进程
if (( psql_process_count < 2)); then
kill ${nmon_pid}
kill ${perf_pid}
current_time=$(date "+%Y-%m-%d %H:%M:%S")
echo "killed perf process and nmon process, time: $current_time"
break
fi
sleep 10
done