乐趣区

关于clickhouse:技术分享-ClickHouse-GDB-调试笔记

作者:xuty

本文起源:原创投稿

* 爱可生开源社区出品,原创内容未经受权不得随便应用,转载请分割小编并注明起源。


一. 背景

记录下第一次应用 GDB 调试 ClickHouse 源码的过程,这里仅仅是通过简略的调试过程理解 ClickHouse 外部的机制,有助于解决纳闷,代码小白,有谬误见谅。

二. 调试问题

调试 ClickHouse 次要是为了解决集体遇到的一个理论问题,上面先形容下这个问题:

  1. 通过 clickhouse 自带的 mysql 表函数 导入全量数据时(这里建了一张测试表memory_test50w 行数据 56G),因为超过最大的内存限度(CK 服务器 36G 内存),导致了如下报错。
localhost :) insert into `test`.`memory_test`  select * mysql('192.168.1.1:3306','test','memory_test','root','xxxx');                                                  

Received exception from server (version 20.8.12):
Code: 241. DB::Exception: Received from 127.0.0.1:9000. DB::Exception: Memory limit (total) exceeded: would use 35.96 GiB (attempt to allocate chunk of 17181966336 bytes), maximum: 28.14 GiB: While executing SourceFromInputStream. 
  1. 大抵过程就是 ClickHouse 会将 mysql 的数据读入内存而后批量写入,这里波及到一个 buffer 的问题,ClickHouse 会读入 多少行数据或者多少 bytes的数据后再批量写入,依据 show processlist 察看来看,貌似达到 100 多 w 行数据就会写入一次,随后开释内存,再循环读写。那么咱们的问题其实读取 MySQL 原表中 100 多万行 的数据超过了咱们 ClickHouse 内存配置大小。

  1. 对于这个问题,如果你 CK 服务器内存配置比拟大其实是不会遇到的,我这里 CK 服务内存仅为 32G,所以可能碰到这个内存问题,最简略的其实扩容下内存就行了,然而为了防止有些我的项目上不好进行内存扩容,所以须要想下其余办法解决。

    • 最开始想到的是用swap 分区,然而理论测试下来,ClickHouse 只会用物理内存,不会用到虚拟内存。
    • 第二个形式是通过where 条件 + 分页查问 mysql,减小内存占用的峰值,实测无效,然而比拟麻烦。
  2. 接着我想着是不是有什么参数能够管制这个批量写入的阀值,这样不就不会遇到内存不够的问题了嘛,100w 会超过内存限度,50w 应该就不会超过了把。然而我 google 了下并没有找到对应的参数,群里问了下也没人晓得,没方法,只能去源码里找找看,这个值到底是不是写死的。

三. 打印栈帧

首先咱们要通过 pstack 打印下堆栈信息,不然无奈晓得函数入口在哪,在这之前须要咱们额定装置下对应 ClickHouse 版本的 clickhouse-common-static-dbg 的 rpm 包(调试库),不然堆栈信息会比拟简陋,而且前面 GDB 调试也会有问题。

比方我的 ClickHouse 版本为 20.8.12.2,那么对应的 rpm 包就为clickhouse-common-static-dbg-20.8.12.2-2.x86_64.rpm

再 ClickHouse 中执行 insert 语句后,通过 pstack + clickhouse 过程 pid > /opt/ck_pstack.log 导入到一个日志文件中。

四. GDB 调试

GDB 不多介绍,不过集体更喜爱应用CGDB,应用 Yum 装置即可,我应用的 OS 版本是 CentOS7.9。

应用 GDB 调试前,还须要将对应 ClickHouse 的源码下载后解压到 /build/ 目录下(默认的编译目录)。

而后调试步骤大略是:

  1. 首先新建个窗口,clickhouse-client 连贯进入 ClickHouse,期待执行 SQL。
  2. 关上 CGDB,attach 到 Clickhouse 的 pid 上,在对应函数行打上断点,这里抉择的是DB::SourceFromInputStream::generate(从栈帧中抉择),CGDB 中须要配置疏忽信号量,不然 CGDB 会始终断开。
(gdb) att 1446
(gdb) handle SIGUSR2 noprint nostop
Signal        Stop      Print   Pass to program Description
SIGUSR2       No        No      Yes             User defined signal 2
(gdb) handle SIGUSR1 noprint nostop
Signal        Stop      Print   Pass to program Description
SIGUSR1       No        No      Yes             User defined signal 1
(gdb) b DB::SourceFromInputStream::generate
Breakpoint 1 at 0x16610f40: file ../src/Processors/Sources/SourceFromInputStream.cpp, line 135.
  1. 在第一步骤关上的窗口中执行 insert 语句。
  2. CGDB 中按 c 持续,就会跳到 generate 函数上

  1. 接着就是缓缓 n,打印参数,一步一步看代码流程。

五. max_block_size

这里间接上调试发现的后果,当读取的行数等于 max_block_size 的时候,就会跳出循环读取,批量写入 ClickHouse,开释内存,这个 max_block_size 的 GDB 打印的值为1048545,看着十分像是一个能够配置的参数。

搜寻源码中 max_block_size 的赋值,上面这段看着比拟像,由 min_insert_block_size_rows 配置参数决定。

接着从 system.settings 表中搜寻了下,发现 min_insert_block_size_rows 这个参数的形容和默认值的确都十分像,根本确定这个参数就会影响批量写入的行数。

六. 测试

在会话中批改参数为 1w,而后执行 insert,能够跑通,而且不会报错。

localhost :) set min_insert_block_size_rows = 10000;

0 rows in set. Elapsed: 0.001 sec. 

localhost :) insert into `test`.`memory_test`  select * from mysql('192.168.213.222:3306','test','memory_test','root','xxxx');                                                  

INSERT INTO test.memory_test SELECT 
    *
FROM mysql('192.168.213.222:3306', 'test', 'memory_test', 'root', 'xxxx')
Ok.

0 rows in set. Elapsed: 2065.189 sec. Processed 500.00 thousand rows, 51.23 GB (242.11 rows/s., 24.81 MB/s.) 

show processlist 也能够看到的确是 1w 行就写入,那么就不会再产生内存不足的问题,至此这个问题根本解决。

退出移动版