我的应用程序处理事务并在磁盘/闪存上创建文件.当应用程序正在进行事务处理时,会创建必须附加到磁盘/闪存上的文件的附加块.应用程序还需要在处理新事务时频繁读取此文件的块.除了还要创建必须附加到此文件的新块之外,每个事务可能还需要从该文件中读取不同的块.有一个传入的事务队列,应用程序可以继续处理队列中的事务,以创建足够深的IO操作管道,以隐藏读取访问的延迟或磁盘或闪存上的写入完成.对于尚未写入磁盘/闪存的块读取(由先前事务放入写入队列),应用程序将停止,直到相应的写入完成.
我有一个重要的性能目标 – 应用程序应该产生尽可能低的发布IO操作的延迟.我的应用程序大约需要10微秒来处理每个事务,并准备对磁盘/闪存上的文件发出写入或读取.发出异步读取或写入的额外延迟应该尽可能小,以便应用程序可以在每次事务处理时尽可能接近10个usecs,只需要进行文件写入时完成每个事务的处理.
我们正在尝试使用io_submit发出写入和读取请求的实现.我将不胜感激任何有关我们要求的最佳方法的建议或反馈. io_submit会给我们最好的表现以达到我们的目标吗?我应该期望每个写入io_submit的延迟和每个读取io_submit的延迟?
使用我们的实验代码(在2.3 GHz Haswell Macbook Pro,Ubuntu Linux 14.04上运行),我们在扩展输出文件时测量写入io_submit大约50个usecs.这太长了,我们甚至没有接近我们的性能要求.任何帮助我以最小延迟启动写请求的指南都将非常感激.
首先,Linux KAIO(io_submit)几乎总是阻塞,除非O_DIRECT打开且不需要扩展区分配,如果O_DIRECT打开,则需要在4Kb对齐边界上读取和写入4Kb倍数,否则强制设备读取-modify写.因此,除非您将应用程序重新架构为O_DIRECT且4Kb对齐i / o友好,否则您将无法获得Linux KAIO.
其次,永远不要在写入期间扩展输出文件,强制扩展分配并可能强制元数据刷新.而是将文件的最大范围分配给某个适当大的值,并保留文件末尾的内部原子计数器.这应该将问题简化为仅限于ext4批量和懒惰的分配 – 更重要的是,你不会强制元数据刷新.这应该意味着ext4上的KAIO大部分时间都是异步的,但不可预测的是会同步,因为它会延迟对期刊的延迟分配.
第三,我可能解决你问题的方法是使用没有O_DIRECT和O_SYNC的原子追加(O_APPEND),所以你要做的是将更新附加到内核页面缓存中不断增长的文件,这是非常快速和并发的安全.然后,您不时地使用fallocate(FALLOC_FL_PUNCH_HOLE)来垃圾收集日志文件中的哪些数据是陈旧的并且可以释放其范围,因此物理存储不会永远增长.这推动了将内存中的写入合并到内核上的问题,其中花费了大量精力来实现这一点,并且因为它总是向前进行写入,所以您将看到写入按照与编写它们的序列非常接近的顺序命中物理存储.使功率损耗恢复简单明了.后一种选择是数据库如何做到这一点,并且实际上日志文件系统就是这样做的,尽管你可能需要对你的软件进行大量的重新设计,但这种算法已被证明是在通用问题情况下延迟与耐久性的最佳平衡.
如果以上所有内容看起来都很多,那么操作系统已经将所有这三种技术集合在一起,形成一个高度优化的实现,更好地称为内存映射:4Kb对齐i / o,O_DIRECT,永不扩展文件,全部异步i / o.在64位系统上,只需将文件定位到非常大的数量并将其映射到内存中.根据需要进行读写.如果您的i / o模式混淆了可能发生的内核页面算法,那么您可能需要在这里和那里使用madvise()来鼓励更好的行为. madvise()更少,更信任我.
很多人尝试使用各种O_DIRECT算法复制mmaps而没有意识到mmaps已经可以完成你需要的一切.我建议先探索一下,如果Linux不会尝试FreeBSD,它有一个更可预测的文件i / o模型,只有这样才能深入研究推出自己的i / o解决方案的领域.作为一整天都在做这些事情的人,我强烈建议你尽可能地避免它们,归档系统是古怪和奇怪行为的恶魔坑.将永无止境的调试留给其他人.