那些年解的疑难性能问题 --- ext4_fsync优化

引子

性能问题有时候不像稳定性问题那样,出了bug, ok, fix该bug,搞定它就行了。性能问题如果涉及到文件系统自身架构方面缺陷的话,是很难解的。

不过通过解这些性能问题,使我慢慢地熟悉了文件系统工作原理和架构设计,慢慢地会发现之前看不懂的内核ext4/f2fs社区那些技术邮件交流,自己现在能全部看懂了,而且还可以跟大牛搞技术合作了。

问题爆出

手机操作系统设计中,对前台任务的时延要求是比较苛刻的。比如Android系统,谷歌花了大精力在优化前台任务的时延,使得用户操作手机上app时,可以无卡顿。

但是前台任务一旦执行fsync调用,有时候会卡在这个调用等待很长时间,fsync才会返回,这样fsync一旦耗时,前台任务就耗时,轻则用户操作手机时,会感受到卡顿现象。重则,Android框架层会触发watchdog事件,然后引发安卓自身重启。

看来必须要优化fsync耗时了。

优化工作开展

紧跟业务

首先我们的性能优化工作必须紧跟公司项目组业务。能够快速熟悉并按照公司业务特点做事,这比单纯的技术突破更重要。

而我们项目组的业务特点:就是通常互联网公司的开发模式,讲究做事短,平,快,不会给你太多时间做技术创作研究,另外性能工作有个特点,优化效果要用性能数据去证明。

经过分析调研该fsync耗时问题,发现是ext4+jbd2架构方面的缺陷导致的,具体如下:

ext4里面的fsync执行到最后一步时,需要等待jbd2把该fsync'er对应的事务给处理完毕,才能返回。如果jbd2恰巧处在高负荷的任务工作状态时,jbd2就无法快速返回,那么fsync不得不做漫长等待工作,结果造成了fsync自身的耗时。

这个问题是ext4里面的fsync和jbd2强耦合造成的,社区ext4 maintainer ted也承认是这个强耦合是ext4的一个架构方面的缺陷,目前不好调整它。

所以面对这个棘手的fsync耗时问题,只有采取通用的迭代式开发模式了,先着力突破解决一个问题,快速出个优化方案第一版,然后再迭代不断持续地去优化该fsync耗时问题。

异步discard优化

经过调研,发现用户那边的一个问题场景是在清理删除一些小文件后,再操作手机会有卡顿现象出现。对现场的bugreport log分析,发现卡顿正是由于fsync耗时引起的,而fsync慢是由于jbd2里面执行了很多ext4实时discard工作。

执行单个discard,并不耗时,但是在删除很多小文件后,jbd2 commit线程里面就会积累很多这样的discard工作请求,需要jbd2去完成。这样jbd2工作耗时,导致了fsync也跟着耗时了。

有了详细地问题原因结论后,下来想出了第一个优化方案:异步discard。

discard工作是Linux存储领域经常讨论的一个热门优化课题。在有些存储芯片上,累计的大量discard命令的处理是比较耗时的。但是还是得必须做discard工作的,因为该工作对于flash芯片的保养维护,存储IO性能优化方面还是很重要的。

所以经过精心设计,又加上本地的性能测试效果验证,发现异步discard的确可以提升fsync的性能,同时又能满足flash芯片必须及时做discard工作的需求。

技术方面的成长

1:通过该优化方案,使得我对存储芯片做discard的过程,还有ext4 discard触发的用户场景和整个工作流程都有深入的熟悉和掌握了。

2:对存储芯片如何做trim工作有了熟悉。这个涉及到存储性能优化工作的特色:可以跟存储芯片厂商密切配合,对存储芯片工作机制也会熟悉的。因为我们的业务是做存储性能优化的,所以很关注对存储芯片做完异步discard后,究竟过多久会带来IO性能的提升,答案是在存储芯片做完gc后,下来再测试存储性能,就会有提升。

需要指出的是该优化方案,还使得我们存储团队收获了第一篇能够发表在国际顶级会议:acm-mobi上的论文,向公司证明了我们存储团队的技术实力。

sqlite数据库 io优化

前一个优化方案部署完后,接着又发现用户手机还是经常报fsync卡顿问题。于是经过对手机操作系统fsync下发的调研,又发现了手机操作系统存储方面的一大特点:

手机操作系统里面大部分写都是数据库sqlite的单笔小量数据的频繁同步随机写(就是write+fsync)。

所以用户在日常使用手机时,尤其是重度使用时,用户态sqlite会频繁下发fsync给内核,fsync触发次数多了,那么必定会加重单次fsync耗时的。所以下来该优化sqlite的fsync调用次数了。

经过对sqlite代码的调研,发现sqlite数据库的journal defaul mode和sync default mode都是可以优化的。

sync default mode目前是full ,该模式下会频繁下发fsync的,可以换成normal模式的,这样对于wal的数据库,可以只在checkpoint的时候,做fsync工作,其他时候不需要做了。

journal default mode目前是truncate, 可以改成persist的。这样可以减少数据库IO方面的元数据写入量。(通过打开底层ftrace log,可以清晰地看到改成persist后,sqlite的元数据写入量会减少很多)。

经过对sqlite io的优化后,系统的下发fsync次数的确减少了,fsync耗时问题又得到了优化。

技术方面的成长

对sqlite数据库日志方面的设计思路和工作特点,以及与ext4文件系统搭配的缺陷问题,都有了比较深入地理解和掌握。

ps:

从上面的异步discard优化效果调研,还有sqlite性能优化,还有下面的fsync优化工作,包括之前的ext4碎片整理方案,这些性能优化无不诠释了存储性能优化工作的一大特点:

和解稳定性问题有一个大的区别,稳定性问题多是系统某个地方出问题了,我们fix它,不让它出故障就行了。解稳定性问题很多时候很难有系统整体架构方面的视野观。

但是存储性能优化则不一样了,我们搞个性能优化方案,往往不仅仅关注文件系统本身,要关注的是整个IO栈,而且还会横向关注比较不同文件系统间的设计差异。这样对存储整体设计架构方面,自身会开拓不少视野。

比如部署异步discard方案,不仅仅要关注内核软件IO栈,还有跟存储芯片厂商沟通,关注存储芯片里面的实际优化效果。

sqlite方面,要做性能优化,会关注它的整体设计架构方面缺陷,比如它和ext4文件系统搭配起来的双日志性能缺陷,于是横向比较f2fs,这个新文件系统的atomic写怎么解除双日志缺陷。

被推翻的走捷径的优化

前面两个优化做完后,发现用户手机还是会有fsync卡顿问题出现。

前面已经说了这个ext4 fsync耗时其实关键是等待jbd2事务commit完成这一步比较耗时,那么能否不等待jbd2 commit事务完成,直接返回呢,这样fsync工作跟jbd2就没有关系了,然后再做成手机低电量时,恢复fsync的正常执行。

带着这样的疑问,调研了异常掉电时文件系统一致性问题,还有ext4日志对于保障文件系统数据和元数据一致性问题方面的的东西,最后结论是还是不要采取这种走捷径的优化思路。

理由如下:

不能强行依赖手机电量信息去调整fsync的行为,fsync行为一旦调整,文件系统日志行为也跟着被变更,万一电量计算不准或者手机异常掉电重启,文件系统的一致性就有可能会遭到破坏。

有人可能会说为啥不在手机内核panic时,或者异常掉电之前那段时间,强行执行下sync操作,同步下文件系统的数据。这样做也是有风险的,首先手机上的存储器件有时候会出现各种意外问题,执行sync操作时,可能会耗时卡住。

这个时候,异常掉电时,sync操作可能不会及时刷完所有的数据到盘上。另外内核panic因为sync操作耗时卡住时,芯片watchdog不会给os这么充足时间的,会直接复位重启的。

所以最好还是不要采取这种走捷径的优化。

技术方面的成长

对ext4如何借助jbd2来实现日志功能,以及日志功能怎么保障文件系统元数据一致性,还有异常掉电文件系统丢数据问题有了更深刻地理解和掌握。

结合社区jbd2相关patch的优化

做Linux存储性能优化工作,需要经常紧跟一些开源存储社区动态,比如要关注ext4/f2fs mail list上面每天进的优化patch,关注内核lwn社区上面最新发表的内核方面文章,要会充分利用这些开源资源,为自己的kpi考核服务。

凑巧ext4社区上发布了优化jbd2工作性能的patch:jbd2_inode dirty range scoping,这个patch经过我的分析后,认为可以优化我们碰到的fsync耗时问题。

经过对该patch代码的深入研读,结合着长期跟踪分析这个ext4 fsync 耗时问题的工作经历,

终于发掘到了我们的手机系统引发fsync耗时的又一大原因

手机的ext4文件系统会被设置成order 日志模式 + 物理block延迟分配,而做这样的设置后,会加重单次fsync耗时的。因为order模式下,jbd2在commit事务时不得不去等待文件脏数据落盘完成,

这一等待是比较耗时的,尤其在ext4开启延迟分配后,系统的刷脏页时间周期会延长至30秒,这样在内核回写线程被周期性唤醒后,需要刷系统累积的大量脏页时,还得做耗时的物理block分配工作,这样刷脏页就会消耗时间,

这样jbd2也不得不卡在等刷脏页完成这步。然后呢,fsync也不得不跟着卡住等待。

而社区这个changes,正是优化这个因jbd2等待脏页回写完成而导致的性能问题的。通过对该patch的学习,我又发现了该patch的不足之处,又做了进一步地优化。结果这个优化方案完成后,手机的fsync卡顿又减少了。

技术方面的成长

1:对jbd2 order工作模式和内核脏页回写线程,以及ext4延迟分配的工作机制和缺陷都有了深刻地理解。

2:结合社区patch,又发现了两点不足之处,做出的改进如下:

1)区分dirty ranges for each of the current and next transaction

2)去掉内核writeback线程的第一个blk request plug。

进一步的优化

前面一些优化方案部署后,还是会存在有fsync耗时问题。看来需要继续优化了。

1:尝试调研jbd2日志模式由order变为writeback

目前的fsync耗时问题很多都是jbd2工作在order模式下引发的,能否尝试换成writeback模式呢,这样jbd2就不需要等待脏数据回写完成了,自身工作负担也减轻很多。

经过对手机业务场景的调研,发现现在手机里面都是加密文件系统,改成writeback模式不用担心数据安全泄密问题的。这样改成writeback的一个路障被打通了。

接下里对社区历年来的jbd2提交patch又做了调研,最后发现还不能改成writeback模式。

因为除了有stale data问题之外,还会有数据稳定性问题。下来又在ext4社区跟ted咨询了下,结果确认确实会存在数据稳定性风险的。

2:比较ext4在日志保护文件系统数据稳定性方面和f2fs的区别

组内同时也在做f2fs文件系统的工作,有机会在文件系统数据稳定性方面对ext4和f2fs做个对比分析。

ext4是原地更新写,所以得借助jbd2和事务的思想,去实现日志功能,保证异常掉电时的文件系统一致性。

f2fs采用了较新的文件系统设计思想,采用cow思想,这样可以部分不需要借助日志功能,也可以保证异常掉电时的文件系统一致性。

有篇lwn的文章更好地诠释了这方面的差别:

Soft updates, hard problems

Soft updates, hard problems [LWN.net]

所以对f2fs如何保证掉电文件系统一致性这方面也做了调研,顺便熟悉了f2fs文件系统的设计架构。

3:开启dioread_nolock的优化

意外地发现这个dioread_nolock开启后,可以切断fsync与内核writeback线程下发的异步io请求之间的联系,因此可以间接提升fsync的性能。

而我们的手机内核版本在kernel-4.9以后,dioread_nolock已经等同于变成空操作了,开启它,对系统的行为没有一点影响,除了可以切断fsync跟内核脏页回写线程的联系。

只是开启后,会有一点负作用就是:会比以前增加点jbd2产生的元数据写入量。

仔细分析了手机的业务场景,发现能解决fsync耗时带来的收益远大于产生的这些额外的元数据写入量。

所以开启这个dioread_nolock后,jbd2不需要等待文件脏数据回写完成了,fsync跟内核脏页回写线程间的联系被切断了fsync耗时性能就得到进一步大地提升了

4:参与ext4社区fast_commit功能开发

ext4社区下来也发现了这个fsync和jbd2强耦合的缺陷,虽然上面的dioread_nolock能解决这个缺陷导致的fsync问题,但是还解决地不够完美,毕竟有点负作用:带来一些额外的元数据写入量。

所以社区maintainer ted开始推动解决这个强耦合缺陷,我也在工作之外业务时间,有幸参与了部分开发工作,帮着ted和一个印度工程师review fast commit代码,提一些建设性的问题。

毕竟这个fast_commit功能一旦实现好,对我们的手机前台任务卡顿问题也会有所优化的。

通过和ted的邮件交流,使我对文件系统日志方面工作机制又有了深刻地理解和认识。

  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值