• 即将写入MySQL源码的官方bug解决之路


    作者 周信静,毕业于浙江大学,目前在CDB/CynosDB数据库内核团队参与TXSQL云数据库内核研发工作,参与了热点行更新以及一系列性能优化工作,并修复了多个MySQL官方bug。

    Part1 背景

    InnoDB的自适应哈希索引(Adpative Hash Index,以下简称AHI),是一种建立在B树索引结构上的索引结构,目的是为了进一步降低BTree的查询代价。

    在B树中搜索一个记录时,需要从根节点下降到叶子结点,同时在每个节点中还需要使用二分查找定位。而AHI对此的改进在于它对BTree索引频繁访问的叶子的行记录建立哈希索引,这样在执行B树查询时,通过AHI就可能能定位到叶子结点上的记录位置,避免B树根节点到叶子结点的下降过程,减少了CPU开销。

    由于AHI的构建是一个自适应且动态的过程,需要根据查询负载访问模式的变更、页面的换入和淘汰等情况做AHI对应清理或者重建,所以本质上来说AHI也是一个cache,具体的构建逻辑网络上也有很多文章讲解,不是本文讨论的重点。

    本文要讨论的是一个鲜为人知的AHI构建锁冲突问题以及相应优化。

    Part2 问题

    TXSQL 5.7版本在跑sysbench时,我们观察到一个非常有意思的现象。

    实验环境是这样的,2台96-core的机器,分别作为sysbench client和mysql server,我们配置buffer pool大小为200GB,同时生成一张120GB的sysbench table。

    如下图所示,我们执行128并发的oltp_read_only负载时,观察到QPS首先有一个上升的坡,这段时间我们发现系统有大量的读IO,正在填充buffer pool,属于正常状态。

    然后过了100s时突然出现了一个急剧的下降,在400s后开始系统QPS开始缓慢上升,直到800s后达到峰值。

    image.png

    通过perf工具抓取系统在QPS剧降时间点的状态,结果如下图:

    image.png

    分析堆栈,可以发现,大量CPU花费在在AHI的hash table的锁竞争上。

    仔细分析不难发现,这个时候大多数页面基本上还没有建立AHI,然后多个线程同时需要对页面建立AHI索引,而这个构建过程需要对同一个AHI hash table加X锁,因此造成了大量等待。

    从QPS变化的角度,可以有如下图所示的分析:

    image.png

    Part3 优化

    我们注意到,对于一个BTree索引来说,其AHI构建是在BTree叶子结点定位完毕后发生的,对应调用链如下:

    btr_cur_search_to_nth_level→ btr_search_info_update→ btr_search_info_update_slow→ btr_search_build_page_hash_index

    在btr_search_info_update_slow中,根据统计信息作出决定,调用btr_search_build_page_hash_index把当前页面的记录加入AHI的hash table,这个过程需要独占hash table的X锁。

    既然只能有一个线程对hash table进行修改,那么其他并发构建AHI线程等待这个hash table的X锁是相当不明智的,因为这样block住了查询的关键路径,同时只有一个线程在做这个构建工作。

    同时我们又注意到AHI只是一个辅助cache,其实用BTree也是能够正确处理查询的。

    那么很自然的,我们可以想到如下的优化方式:

    1. 当我们在BTree查询路径上经过分析后决定要对某一页构建AHI索引时,我们首先看一下该BTree所对应的hash table的锁是否被其他线程拿住了写锁;

    2. 如果被拿住了写锁,我们取消这次针对页的AHI索引构建任务,等待下次再次访问到该页时再尝试去构建,fallback到普通的BTree查询。

    Part4 具体实现

    从实现角度来说,其实非常简单:在btr_search_info_update_slow根据统计信息判断要对一页的记录建立AHI索引时,我们加入一个条件判断:如果当前有并发AHI构建线程拿住了hash table的X锁,我们直接返回即可。

    代码只有几行,大致如下:

    image.png

    有人可能会担心这样直接跳过会不会影响代码正确性?

    答案是否定的,因为我们这里没有清除该页面关于AHI的任何统计信息,只是推迟了构建时机,即推迟到hash table锁冲突不严重的时候再进行。

    Part5 效果

    应用上述的优化后,我们重新执行上述实验,得到如下的结果图:

    image.png

    其中,红线(开启AHI+Contention Avoidance优化)是我们实现上述优化后结果,经过100s左右的预热后,性能稳定,锁瓶颈消失。

    Part6 灵感来源

    其实在原始的AHI查询路径上已经有一个类似的优化了:

    在btr_cur_search_to_nth_level中执行AHI查询前,如果发现AHI的hash table被其他线程X锁住了,直接fallback到BTree查询。

    这里的优化考量是类似的:与其等待AHI的hash table的X锁,不如直接走btree搜索,代价很可能比等待X锁更低,并发度更高。

    image.png

    Part7 总结

    该优化目前已经在TXSQL5.7最新版本中上线,将会有效缓解AHI构建的锁竞争问题,可能的场景包括不限于:系统启动、AHI开关刚开启、主备切换时,所有页面都还没有AHI记录,高并发可能导致大量的AHI构建工作。

    同时经过我们验证,在官方MySQL的5.7和8.0最新版本中都存在该问题,因此我们也已经将这个优化思路贡献给了官方, https://bugs.mysql.com/bug.php?id=100512 ,目前正在评估,相信不久将合入主线。

    本文由博客一文多发平台 OpenWrite 发布!

  • 相关阅读:
    题解 P1003 【铺地毯】
    题解 P1000 【超级玛丽游戏】
    题解 P1036 【选数】
    题解 P1217 【[USACO1.5]回文质数 Prime Palindromes】
    题解 AT934 【完全数】
    题解 P2044 【[NOI2012]随机数生成器】
    题解 P4277 【河城荷取的烟花】
    System.Linq.Dynamic.Core
    008 TBS Studio 真机开发辅助工具的简单说明
    webpack
  • 原文地址:https://www.cnblogs.com/tencentdb/p/13995901.html
Copyright © 2020-2023  润新知