在SSD上写入仅附加文件的最佳方式

Elo*_*off 8 c++ linux filesystems solid-state-drive

我想知道登录SSD的最佳方式是什么.想象一下像数据库日志这样的东西,你只写附加,但你也必须fsync()每个事务或少数事务,以确保应用程序级数据的持久性.

我将介绍SSD如何工作的背景知识,所以如果你已经知道了这一切,那么无论如何我都应该略读它以防万一我错了.进一步阅读的一些好东西是Emmanuel Goossaert的6部分SSD编码指南和论文" 不要将您的日志堆叠在我的日志上"[pdf].

SSD仅在整页中写入和读取.页面大小因SSD而异,但通常为4kb的倍数.我的三星EVO 840使用了8kb的页面大小(顺便提一下,Linus用他通常的多彩方式称之为"无法使用的狗屎".)SSD无法就地修改数据,他们只能写入免费页面.因此,结合这两个限制,更新我的EVO上的单个字节需要读取8kb页面,更改字节,并将其写入新的8kb页面并更新FTL页面映射(ssd数据结构),以便该页面的逻辑地址正如操作系统所理解的那样,现在指向新的物理页面.因为文件数据在相同的擦除块(可以擦除的最小页面组)中也不再是连续的,所以我们也在构建一种碎片债务形式,这将使我们在未来的SSD垃圾收集中付出代价.非常低效.

作为一个助手,看看我的PC文件系统:C:\WINDOWS\system32>fsutil fsinfo ntfsinfo c:它有512字节的扇区大小和4kb的分配(集群)大小.两者都没有映射到SSD页面大小 - 可能效率不高.

只是写入pwrite()内核页面缓存并让操作系统处理写出来的问题.首先,sync_file_range()在调用pwrite()实际启动IO 之后,您需要发出一个额外的调用,否则它将一直等到您调用fsync()并释放IO风暴.其次fsync() 似乎阻止write()对同一文件的未来调用.最后,你无法控制内核如何将内容写入SSD,它可能做得很好,或者它可能做得不好导致大量写入放大.

由于上述原因,并且因为我还需要AIO来读取日志,所以我选择使用O_DIRECT和O_DSYNC写入日志并完全控制.

据我了解,O_DIRECT要求所有写入与扇区大小和整个扇区对齐.因此,每当我决定向日志发出附加内容时,我需要在末尾添加一些填充以将其添加到整个扇区(如果所有写入始终是整个扇区,它们也将正确对齐,至少在我的代码中.)好吧,那不是那么糟糕.但我的问题是,围绕一大堆SSD页面而不是扇区不是更好吗?据推测,这会消除写入放大?

这可能会占用大量空间,特别是如果一次向日志中写入少量数据(例如几百字节).这也可能是不必要的.像三星EVO这样的SSD有写缓存,它们不会在fsync()上刷新它.相反,他们依靠电容器在断电时将缓存写入SSD.在这种情况下,也许SSD做正确的事情,只有一个附加日志一次写入扇区 - 它可能不会写出最后的部分页面,直到下一个附加到达并完成它(或除非它被强制淘汰)由于大量不相关的IO导致缓存.)由于答案可能因设备和文件系统而异,有没有办法可以编写这两种可能性并测试我的理论?在Linux上测量写入放大或更新/ RMW页面数量的一些方法?

Dam*_*ien 3

我会尽力回答你的问题,因为我也有同样的任务,但在 SD 卡中,它仍然是闪存。

简答

您只能在闪存中写入 512 字节的整页。鉴于闪存的写入次数较少,驱动器芯片会进行缓冲/随机化以延长驱动器的使用寿命。

要在闪存中写入一个位,您必须先擦除它所在的整个页(512 字节)。因此,如果您想在某处追加或修改 1 个字节,首先必须擦除它所在的整个页面。

该过程可以概括为:

  • 将整个页面读取到缓冲区
  • 使用添加的内容修改缓冲区
  • 擦除整个页面
  • 使用修改后的缓冲区重写整个页面

长答案

扇区(页)基本上取决于闪存实现的硬件和闪存物理驱动程序,您无法控制它们。每次更改某些内容时都必须清除并重写该页面。

您可能已经知道,如果不清除并重写整个 512 字节,则无法重写页面中的单个位。现在,闪存驱动器在扇区损坏之前的写入周期寿命约为 100,000 次。为了提高寿命,通常是物理驱动程序,有时系统会有写入随机化算法,以避免总是写入相同的扇区。(顺便说一句,永远不要对 SSD 进行碎片整理;它没有用,最多会缩短使用寿命)。

关于集群,这是在与文件系统相关的更高级别上处理的,并且您可以控制这一点。通常,当您格式化新硬盘时,您可以选择簇大小,在Windows上,簇大小是指格式化窗口的分配单元大小。

胖32格式

据我所知,大多数文件系统都使用位于磁盘开头的索引。该索引将跟踪每个簇以及分配给它的内容。这意味着一个文件将至少占用 1 个扇区,即使它小得多。

FAT32

现在的权衡是扇区大小较小,索引表较大并且会占用大量空间。但如果你有很多小文件,那么你会有更好的占用空间。

另一方面,如果您只存储大文件,并且想要选择最大扇区大小,则只需略高于您的文件大小即可。

由于您的任务是执行日志记录,因此我建议登录具有大扇区大小的单个大文件。尝试过这种类型的日志后,单个文件夹中包含大量文件可能会导致问题,特别是在嵌入式设备中。


执行

现在,如果您对驱动器具有原始访问权限并且想要真正优化,则可以直接写入磁盘而不使用文件系统。

好的一面是 * 将为您节省相当多的磁盘空间 * 如果您的设计足够智能,则可以在发生故障时提供磁盘容错能力 * 如果您使用的系统有限,则需要的资源要少得多

缺点 * 更多的工作和调试 * 驱动器不会被系统本身识别。

如果您只记录日志,则不需要文件系统,您只需要一个写入数据的页面的入口点,该数据会不断增加。

我在 SD 卡上完成的实现是在闪存启动时保存 100 个页面,以存储有关写入和读取位置的信息。这是在单个页面中保存的,但为了避免内存循环问题,我会在 100 个页面上按循环方法顺序写入,然后使用算法来检查哪一个页面是最后包含最新信息的页面。

位置存储大约每 5 分钟写入一次,这意味着如果发生断电,我只会丢失 5 分钟的日志。在进一步写入之前,还可以从最后的写入位置检查其他扇区是否包含有效数据。

这提供了一个非常强大的解决方案,因为它们不太可能出现表损坏。

我还建议缓冲 512 字节并逐页写入。


其他的

您可能还想检查一些特定于日志的文件系统,它们可能只是为您完成这项工作:日志结构文件系统