ToplingDB posix aio

(一)背景MyTopling 是基于 ToplingDB 的 MySQL,分叉自 MyRocks,ToplingDB 则分叉自 RocksDB,兼容 RocksDB 接口,从而 MyTopling 可以复用 MyRocks 的大部分成果。ToplingDB 和 MyTopling 都已开源。在 ToplingDB 中,我们通过 fiber + io uring 实现了高效 MultiGet IO 并发,为 MyTopling 的 MRR(Multi Range Read) 提供了强有力的支撑。虽然 io uring 诞生至今(2023-02-27)已有数年,但是现实世界中仍有很多用户在使用缺乏 io uring 的旧内核。而 linux native aio 又仅支持 direct io,所以,posix aio 就成了一个最后的选择。(二)posix aioposix aio 最早定义在 posix 标准 POSIX.1-2001 中,其原型或变体的出现时间则更早,历史非常悠久。glibc 对 posix aio 的支持从 2.1 版就开始了(1999年,早于POSIX.1-2001),是用线程池实现的,一直到现在(2023-02-27)的最新版,依然是线程池。在 ToplingDB 的底层库 topling-zip 中,我在最开始就实现了对 posix aio 的支持,但是在实践中一直没有使用。(三)fiber_aio_read 如何使用 posix aio在最开始的时候,先调用 aio_read,然后切换到其它 fiber 发起更多 aio_read,等再次切换回当前 fiber 时,调用 aio_error 查看 aio 的执行状态,直到不再是EINPROGRESS。这种使用方式,是 posix aio 的一个标准用法,我们的单元测试也一切 OK。整合到 MyTopling 中进行测试,除了性能比 io uring 差一点,一切正常。(四)在 centos 7.3 上炸了为了适配旧系统,我们在 centos 7.3 上直接用 MyTopling 测试,不幸的是,很快就炸了,并且炸的时候 stack 全是乱的!接下来,最合理的诊断就是跑单元测试看看炸在什么位置,结果更加让人崩溃:release 版的单元测试跑 10 次,平均下来会挂一两次,其余正常,挂掉的时候,stack 也是乱的debug 版的单元测试跑 100 次,会挂 99 次,stack 也是乱的(五)诊断问题跟新系统相比,编译器是一样的(均使用自己编译的 gcc-12.1),操作系统内核虽然不同,但是 glibc 线程池最终调用的都是 pread,pread 总不会出问题吧。所以,最可疑的就是 glibc,但是 glibc 从 2.1 版就开始支持 aio,这个时间(1999)甚至早于 posix aio 标准的发布时间(2001),到 centos 7.3 的 glibc-2.17,期间经历了十几年,如果真有 bug,也该早都修了。(六)解药:对称之美接连好几天时间,这个问题一直是神龙见首不见尾,从来没有抓到过第一现场。就在我快要放弃,准备重新发明一遍轮子,自己用线程池实现一遍 posix aio 的功能时,又 review 了一遍 fiber_aio.cpp,看到 aio_read 相关代码时,觉得这个太丑,特别是,对 io uring 和 linux native aio 都使用 submit → wait → reap 模型实现了单独的派生类,只有 posix aio 使用的是就地 submit → poll。为了对称,为了美感,把 posix aio 也实现为 submit → wait → reap 模型吧!代码写完之后,确实很符合审美。既然代码都写完了,跑一次看看吧……bug 居然神奇地消失了!从执行流程上看,相比之前只是多了 wait 步骤中的 aio_suspend 调用! int aio_suspend(const struct aiocb * const aiocb_list[],

            int nitems, const struct timespec *timeout);

aio 的任何文档中都没有说调用 aio_error 之前必须调用 aio_suspend,甚至 aio 文档的示例代码中都没有 aio_suspend 调用。从效率上来说,submit → wait → reap 必然是最高效的。以前只是偷了一点懒,觉得 aio_read 返回后先是 fiber yield 切换到其它 fiber (会执行其它 aio_read),等回来调用到 aio_error 检查结果时,已经发起放多个 aio_read 了,而不是一直在那里浪费 CPU 时间 busy poll aio_error。(七)结论至少在 centos 7.3 使用的 glib-2.17 中,没有在 aio_read 中将 aio_error 设置为 EINPROGRESS,而是在随后的某个地方才设置的,导致用户代码调用 aio_error 返回了 success 并继续执行,然后释放了相应的缓冲区,但实际上该 io 并没有执行,等到 glibc 执行该 io 时,传给 pread 一个已经释放了的 buffer,然后一切都乱了……我们在追求美感的过程中,无意中调用了 aio_suspend,正是在 aio_suspend 中,glibc 设置了正确的 aio_error,于是就一切正常了……大概 glib-2.17 之后的某个版本,修复了这个 bug,在 aio_read 中就设置了 EINPROGRESS ,所以不需要调用 aio_suspend

你可能感兴趣的:(后端数据库mysqlredis)