代码之家  ›  专栏  ›  技术社区  ›  Eloff

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

  •  12
  • Eloff  · 技术社区  · 8 年前

    我想知道什么是登录SSD的最佳方式。想想像数据库日志这样的东西,您只需编写append,但还必须对每个事务或几个事务执行fsync(),以确保应用程序级数据的持久性。

    我将介绍一些SSD如何工作的背景知识,所以如果你已经了解了所有这些,请浏览一下,以防我在某些方面出错。进一步阅读的一些好东西是 Emmanuel Goossaert 6-part guide to coding for SSDs 和报纸 Don't Stack your Log on my Log [pdf] .

    SSD仅在整页中写入和读取。SSD与SSD的页面大小不同,但通常为4kb的倍数。我的三星EVO 840使用8kb的页面大小(顺便说一下, Linus calls "unusable shit" 以他一贯的多姿多彩的方式。)SSD无法就地修改数据,只能写入空闲页面。因此,结合这两个限制,更新EVO上的单个字节需要读取8kb页面,更改字节,并将其写入新的8kb页,并更新FTL页映射(ssd数据结构),以便操作系统现在理解的该页的逻辑地址指向新的物理页。由于文件数据在同一擦除块(可擦除的最小页面组)中也不再连续,我们也在积累一种碎片债务,这将使我们在未来的SSD垃圾收集中付出代价。效率极低。

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

    仅仅用例如。 pwrite() 并让OS处理写出来的事情。首先,您需要发布额外的 sync_file_range() 呼叫后再呼叫 pwrite() 实际启动IO,否则它将一直等到您调用 fsync() 并引发IO风暴。其次 fsync() seems to block 未来的呼叫 write() 在同一文件上。最后,您无法控制内核如何向SSD写入内容,这可能会做得很好,也可能会做的很差,导致大量写入放大。

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

    据我所知,O_DIRECT要求所有写入都与扇区大小和整个扇区数对齐。因此,每当我决定向日志发出一个附加时,我需要在末尾添加一些填充,使其达到整个扇区数(如果所有写入都是整个扇区,它们也将正确对齐,至少在我的代码中是这样。)。但我的问题是,将SSD页面取整而不是扇区不是更好吗?可能这会消除写入放大?

    这可能会消耗大量的空间,特别是如果一次向日志中写入少量数据(例如几百字节)。这也可能是不必要的。像三星EVO这样的SSD有一个写缓存,它们不会在fsync()上刷新。相反,它们依靠电容器在断电时将缓存写入SSD。在这种情况下,也许SSD做了正确的事情,一次写入一个仅追加日志的扇区-它可能不会写出最后的部分页,直到下一个追加到达并完成它(或者除非它由于大量不相关的IO而被迫离开缓存)。由于这一问题的答案可能因设备和文件系统而异,有没有一种方法可以将这两种可能性编码起来并检验我的理论?有什么方法可以测量Linux上的写放大率或更新的/RMW页面数?

    1 回复  |  直到 8 年前
        1
  •  3
  •   Damien    5 年前

    我会尝试回答你的问题,因为我有同样的任务,但在SD卡,这仍然是一个闪存。

    简短的回答

    您只能在闪存中写入512字节的整页。考虑到闪存的写入计数很低,驱动芯片正在进行缓冲/随机化,以提高驱动器寿命。

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

    该过程可总结为:

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

    冗长的回答

    扇区(页面)基本上取决于闪存实现和闪存物理驱动程序的硬件,您无法控制这些硬件。每次你改变一些东西时,必须清除并重写该页。

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

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

    Fat 32 format

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

    FAT32

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

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

    因为您的任务是执行日志记录,所以我建议您登录一个大扇区大小的大文件。在尝试过这种类型的日志之后,在单个文件夹中拥有大量文件可能会导致问题,特别是当您在嵌入式设备中时。


    实施

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

    好的一面 *将为您节省相当多的磁盘空间 *如果您的设计足够聪明,将使磁盘在出现故障时具有容错能力 *如果您在有限的系统上,将需要更少的资源

    在不利方面 *更多的工作和调试 *该驱动器不会被系统本地识别。

    如果你只记录,你不需要有一个文件系统,你只需要一个页面的入口点来写入你的数据,这个入口点会不断增加。

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

    位置存储每5分钟左右完成一次,这意味着在停电的情况下,我只会丢失5分钟的日志。在进一步写入之前,还可以从最后一个写入位置检查进一步的扇区是否包含有效数据。

    这提供了一个非常健壮的解决方案,因为它们不太可能发生表损坏。

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


    其他

    您可能还想检查一些特定于日志的文件系统,他们可能会简单地为您完成工作: Log-structured file system