searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

TempTable存储引擎长时间占用写入IO的bug分析

2023-10-27 06:24:36
14
0

一、现象

问题SQL:均为select cl from tb where cl is not null group by cl order by null这一类,样例为select ORDER_NO from q_as_mtl_nmonths_f where ORDER_NO is not null group by ORDER_NO order by null。

如下图,该类SQL的执行时快时慢,差距可达数万倍:

查看系统状态发现,CPU使用异常,操作系统内核占比过高

追踪SQL执行过程发现,缺页异常过多,上下文切换过多,且多为自愿切换,考虑应该是系统调用自愿换出,IO写入数量级异常,考虑该问题与写入相关

二、系统调用分析

根据采样可知,占用CPU的系统调用为write:

统计my_fallocator调用链上的所有系统调用,write的时延正常,调用次数明显异常,但结合open的调用次数发现write/open*4096≈11MB,可知write:open正常,因为每文件的写入落在[1MB, 512MB]的正常范围,因此问题可以归结为open调用过多,由于my_fallocator的调用与open一一对应,故问题可以等价表述为my_fallocator的调用过多

三、运行时状态分析

下图为my_fallocator调用链的运行时调用栈,后续运行时分析皆以此为基础:

handler::ha_write_row是服务层针对insert操作与存储引擎通信的统一接口,跟踪handler::ha_write_row可知,此例中handler::ha_write_row的调用鲜有成功,错误码皆为121,即唯一键冲突,这是因为新扫描到的行的group by字段已经由于先前扫描到的其它行插入到临时表中,这里0x2b1e0a75228追踪的是临时表当前的行数,不多,且调用前后无变化:

追踪该线程(即0x2b22c0a03000)主表的扫描行数(即0x2b23b97f0e88),发现每扫描一行均须调用一次my_fallocator,这是严重的问题,根据TempTable存储引擎的存储管理算法,一次my_fallocator调用的IO最高可达512MB,需要131072次系统调用才能完成,按照每次103微秒的延迟预估,扫描一行主表须要13秒,此即为造成问题现象的直接原因

追查每行的容量需求发现为正常的数十字节,如下图为44字节,每分配数十字节的容量就须要完成512MB的IO,与mysql设计意图明显不符,确定为mysql的bug,图中追踪的m_state._M_ptr->current_block.m_offset为当前分配所属的预分配存储块,每次均为NULL,此即触发该bug的直接原因

四、源码分析

如图,当预分配存储块的最后一个分片被回收时,会将预分配存储块置空,这意味着,下一次分配存储空间时,会重新调用my_fallocator初始化整个块。值得注意的是,如果此处保留预分配存储块不调用munmap而不增加额外的存储管理措施,则会造成资源泄露:

然而,根据前文所述,如图,临时表的插入绝大多数时候报唯一键冲突的错,因此会析构并回收分片,这就导致刚刚预分配完整块后分配的行立即被析构,使得整个预分配块被回收,这两点相结合即为该bug产生作用的完整机制。

四、bug触发条件分析

TempTable存储引擎的存储管理分为三层:第一层为每线程的局部存储,容量为1MB;第二层为全局RAM,容量为temptable_max_ram,默认1GB;第三层为内存文件映射,是由进程空间、操作系统缓存和外存共同组成的复杂存储系统,容量在当前生产版本中不受限制。

要触发本文所述bug,必须先击穿前两层存储,但这里指的击穿是流量击穿而不是容量击穿,因为mysql为了避免整理存储碎片,已分配的空间不能再次使用,必须等待存储块整体回收。本例中,临时表的容量虽不够大,但主表足够大的容量给临时表创造了足够大的流量,在并发执行时足以击穿前两层存储。

如果击穿了前两层存储,要触发bug还必须在创建一个大的预分配块时数据序列正好会报大量连续的唯一键冲突,即bug的触发条件与数据在各存储块的分布高度相关,并且与主表的数据访问序列高度相关。而这两者分别又与数据库进程的运行状态、存储负载、主表的数据集高度相关,从而造成了SQL的执行效率时好时坏,呈现高度随机性的现象。

综上所述,第一层存储的1MB容量对于OLTP型应用无论从容量还是流量的角度来衡量都是安全的,因此对于典型的OLTP型应用该bug无论如何都不会触发。这应该也是该bug至今在mysql最新版本中仍未修复的原因:mysql专注处理OLTP型应用,对于OLAP型应用并不擅长,因此处理OLAP场景缺乏稳定性。

 

0条评论
0 / 1000
曾****江
5文章数
1粉丝数
曾****江
5 文章 | 1 粉丝
原创

TempTable存储引擎长时间占用写入IO的bug分析

2023-10-27 06:24:36
14
0

一、现象

问题SQL:均为select cl from tb where cl is not null group by cl order by null这一类,样例为select ORDER_NO from q_as_mtl_nmonths_f where ORDER_NO is not null group by ORDER_NO order by null。

如下图,该类SQL的执行时快时慢,差距可达数万倍:

查看系统状态发现,CPU使用异常,操作系统内核占比过高

追踪SQL执行过程发现,缺页异常过多,上下文切换过多,且多为自愿切换,考虑应该是系统调用自愿换出,IO写入数量级异常,考虑该问题与写入相关

二、系统调用分析

根据采样可知,占用CPU的系统调用为write:

统计my_fallocator调用链上的所有系统调用,write的时延正常,调用次数明显异常,但结合open的调用次数发现write/open*4096≈11MB,可知write:open正常,因为每文件的写入落在[1MB, 512MB]的正常范围,因此问题可以归结为open调用过多,由于my_fallocator的调用与open一一对应,故问题可以等价表述为my_fallocator的调用过多

三、运行时状态分析

下图为my_fallocator调用链的运行时调用栈,后续运行时分析皆以此为基础:

handler::ha_write_row是服务层针对insert操作与存储引擎通信的统一接口,跟踪handler::ha_write_row可知,此例中handler::ha_write_row的调用鲜有成功,错误码皆为121,即唯一键冲突,这是因为新扫描到的行的group by字段已经由于先前扫描到的其它行插入到临时表中,这里0x2b1e0a75228追踪的是临时表当前的行数,不多,且调用前后无变化:

追踪该线程(即0x2b22c0a03000)主表的扫描行数(即0x2b23b97f0e88),发现每扫描一行均须调用一次my_fallocator,这是严重的问题,根据TempTable存储引擎的存储管理算法,一次my_fallocator调用的IO最高可达512MB,需要131072次系统调用才能完成,按照每次103微秒的延迟预估,扫描一行主表须要13秒,此即为造成问题现象的直接原因

追查每行的容量需求发现为正常的数十字节,如下图为44字节,每分配数十字节的容量就须要完成512MB的IO,与mysql设计意图明显不符,确定为mysql的bug,图中追踪的m_state._M_ptr->current_block.m_offset为当前分配所属的预分配存储块,每次均为NULL,此即触发该bug的直接原因

四、源码分析

如图,当预分配存储块的最后一个分片被回收时,会将预分配存储块置空,这意味着,下一次分配存储空间时,会重新调用my_fallocator初始化整个块。值得注意的是,如果此处保留预分配存储块不调用munmap而不增加额外的存储管理措施,则会造成资源泄露:

然而,根据前文所述,如图,临时表的插入绝大多数时候报唯一键冲突的错,因此会析构并回收分片,这就导致刚刚预分配完整块后分配的行立即被析构,使得整个预分配块被回收,这两点相结合即为该bug产生作用的完整机制。

四、bug触发条件分析

TempTable存储引擎的存储管理分为三层:第一层为每线程的局部存储,容量为1MB;第二层为全局RAM,容量为temptable_max_ram,默认1GB;第三层为内存文件映射,是由进程空间、操作系统缓存和外存共同组成的复杂存储系统,容量在当前生产版本中不受限制。

要触发本文所述bug,必须先击穿前两层存储,但这里指的击穿是流量击穿而不是容量击穿,因为mysql为了避免整理存储碎片,已分配的空间不能再次使用,必须等待存储块整体回收。本例中,临时表的容量虽不够大,但主表足够大的容量给临时表创造了足够大的流量,在并发执行时足以击穿前两层存储。

如果击穿了前两层存储,要触发bug还必须在创建一个大的预分配块时数据序列正好会报大量连续的唯一键冲突,即bug的触发条件与数据在各存储块的分布高度相关,并且与主表的数据访问序列高度相关。而这两者分别又与数据库进程的运行状态、存储负载、主表的数据集高度相关,从而造成了SQL的执行效率时好时坏,呈现高度随机性的现象。

综上所述,第一层存储的1MB容量对于OLTP型应用无论从容量还是流量的角度来衡量都是安全的,因此对于典型的OLTP型应用该bug无论如何都不会触发。这应该也是该bug至今在mysql最新版本中仍未修复的原因:mysql专注处理OLTP型应用,对于OLAP型应用并不擅长,因此处理OLAP场景缺乏稳定性。

 

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0