最近再对clickhouse hive engine进行一些调优工作。本文将总结调优过程中常用的一些工具和命令。

火焰图

火焰图 (Flame Graph) 是性能优化大师 Bredan Gregg 创建的一种性能分析图标,因为它的样子近似火焰而得名。使用火焰图能够非常快速的定位到代码中的瓶颈,它就像一个在代码之海中航行的程序员的地图,指引着性能优化的方向。下图是clickhouse的一张火焰图。

cpu

火焰图的上下表示调用关系,下层的函数调用上层的函数。每个函数对应一个等高的长条,这个长条对应着该函数的某项指标,如cpu time/real time/memory allocate.

根据不同的指标,火焰图可以分为:

  • cpu火焰图。显示代码中函数的cpu耗时,函数宽度与cpu time成正比。主要用于分析进程中的cpu瓶颈
  • real火焰图。显示代码中函数的duration耗时,函数宽度与real time成正比。与cpu火焰图结合可用于分析进程中的io瓶颈
  • mem火焰图。显示代码中函数中申请内存的大小,函数宽度与memory allocation成正比。主要用于优化进程的内存占用。

对于clickhouse来说,性能瓶颈主要在于cpu和io。因此本文着重介绍如何生成cpu和real火焰图

clickhouse-flamegraph

clickhouse-flamegraph(https://github.com/Slach/clickhouse-flamegraph)是一个命令行工具,他能够将clickhouse系统表system.trace_log中的性能指标以query_id为粒度可视化,形成对应的火焰图。

在使用clickhouse-flamegraph之前,需要

  • 安装:参考官方文档,此处略
  • 配置:修改clickhouse配置文件:config.xml和users.xml,参考官方文档。这样做的目的是让clickhouse在query执行过程中,对cpu time, real time, memory allocate等指标进行采样。

安装和配置完成后,如何使用clickhouse-flame-graph生成火焰图呢?

  • 首先执行对应的query,
  • 然后记录对应的query_id
  • 运行clickhouse-flamegraph
$ clickhouse-flamegraph --dsn="http://localhost:8123/?user=default&password=xxx" --query-id=<query_id>
{"level":"info","dsn":"http://localhost:8123/?user=default&password=xxx","time":"2022-06-10T10:28:44+08:00","message":"connected to ClickHouse"}
{"level":"info","sqlFiles":1,"time":"2022-06-10T10:28:44+08:00","message":"write .sql files"}
{"level":"info","processedFiles":2,"time":"2022-06-10T10:28:44+08:00","message":"done processing"}
  • 最后查看火焰图。在当前目录下执行,可以看到以下几个文件
$ tree clickhouse-flamegraphs 
clickhouse-flamegraphs
└── f
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.CPU.svg
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.CPU.txt
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.MemorySample.svg
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.MemorySample.txt
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.Memory.svg
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.Memory.txt
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.Real.svg
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.Real.txt
    ├── 2694aa7a-f421-4727-a789-383a7bce05be.sql

其中,*.Memory.svg对应着mem火焰图,*.CPU.svg对应着cpu火焰图,*.Real.svg对应着real火焰图。*.sql中是本次查询的query语句。

我们看到,使用clickhouse-flamegraph生成火焰图还是很方便的。但是由于clickhouse的cpu time/real time的采样原理是在每个线程中一方面通过定时器每隔一定时间发出SIGUSR1和SIGUSR2 signal,另一方面注册信号处理函数,在收到signal的同时,将当前stack trace记录到system.trace_log中去。

由于clickhouse自带cpu time/real time的实现原理,clickhouse-flamegraph有两个缺点

  • cpu time和real time的采样时间间隔太长。以笔者调试hive engine查询的经验,目前20ms是比较可靠的间隔,间隔再短点就无法保证query正常执行。
  • 开启cpu time和real time采样会明显拖慢query运行速度,导致火焰图失真。

perf

perf是内置于linux kernel中的profiling工具,它能监测各种硬件和软件事件,被广泛用于性能瓶颈的查找和热点代码的定位。正因为他是内置的profiling工具,perf能够避免clickhouse-flamegraph的的上述缺点。perf的缺省采样频率是4000/s, 使用perf也不会导致被观测进程产生明显的性能衰减。

那么如何使用perf生成cpu和real火焰图呢?

  • 首先在clickhouse加上以下编译参数,避免debug信息丢失导致火焰图不准确。

clickhouse在使用-O2编译的时候,会默认带上-fomit-frame-pointer和-fptimize-sibling-calls参数。这些参数通过编译阶段的优化,对运行是的性能有所提升,但是会让debug信息变得不完整。

**-fomit-frame-pointer**

Don't keep the frame pointer in a register for functions that don't need one. This avoids the instructions to save, set up and restore frame pointers; it also makes an extra register available in many functions. It also makes debugging impossible on some machines.

-foptimize-sibling-calls

This option determines whether the compiler optimizes tail recursive calls. It enables conversion of tail recursion into loops.

If you do not want to optimize tail recursive calls, specify -fno-optimize-sibling-calls.

Tail recursion is a special form of recursion that doesn't use stack space. In tail recursion, a recursive call is converted to a GOTO statement that returns to the beginning of the function. In this case, the return value of the recursive call is only used to be returned. It is not used in another expression. The recursive function is converted into a loop, which prevents modification of the stack space used.

if (COMPILER_CLANG)
    # generate ranges for fast "addr2line" search
    if (NOT CMAKE_BUILD_TYPE_UC STREQUAL "RELEASE")
        set(COMPILER_FLAGS "${COMPILER_FLAGS} -gdwarf-aranges")
    endif ()

    if (HAS_USE_CTOR_HOMING)
        # For more info see https://blog.llvm.org/posts/2021-04-05-constructor-homing-for-debug-info/
        if (CMAKE_BUILD_TYPE_UC STREQUAL "DEBUG" OR CMAKE_BUILD_TYPE_UC STREQUAL "RELWITHDEBINFO")
            set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Xclang -fuse-ctor-homing")
            set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Xclang -fuse-ctor-homing")
        endif()
    endif()
	
	# 添加以下两行
	# 
    set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fno-optimize-sibling-calls")
    set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer -fno-optimize-sibling-calls")
endif ()
  • 通过clickhouse-benchmark运行要profile的query。如

./clickhouse benchmark -i 5 --query "select day, hour, appid, count(1) from test.csc_like_all_push_hour_text where day = '2022-02-03' and hour = '00' group by day, hour, appid"

  • 在运行query的同时,执行perf record, 分别对cyclescpu-clock进行采集。其中cycles事件对应cpu time, cpu-clock事件对应real time. 如
perf record -ag -p <clickhouse-pid>  -e  cycles  -- sleep  15
perf record -ag -p <clickhouse-pid>  -e  cpu-clock  -- sleep  15
perf script > out.perf && stackcollapse-perf.pl out.perf > out.folded && flamegraph.pl out.folded > out.svg

perf生成的cpu火焰图的示例:

cpu1

perf生成的real火焰图的示例:

real1

总结

本文介绍了两种为clickhouse进程生成火焰图的方法:clickhouse-flamegraph与perf。clickhouse-flamegraph的优点是操作简单,缺点是被观测进程有性能衰减、采样间隔太长。perf在使用上更通用且更精确,但是操作也更复杂。因此建议将clickhouse-flamegraph用于定性分析,快速判定哪个模块是瓶颈;将perf用于定量分析,定位哪个具体函数是瓶颈,并衡量性能优化后的收益。

参考

https://zh-blog.logan.tw/2019/10/06/intro-to-perf-events-and-call-graph

https://clickhouse.com/docs/en/interfaces/third-party/gui/

https://github.tiankonguse.com/blog/2016/03/29/perf-record.html