【问题标题】:Optimal way of writing to append-only files on an SSD写入 SSD 上仅附加文件的最佳方式
【发布时间】:2016-08-17 09:24:40
【问题描述】:

我想知道登录到 SSD 的最佳方式是什么。考虑一下数据库日志之类的东西,您在其中只编写追加,但您还必须对每个事务或少数事务进行 fsync() 以确保应用程序级数据的持久性。

我将提供一些有关 SSD 工作原理的背景知识,因此,如果您已经了解所有这些,请先略读一下,以防我对某些事情有误。一些值得进一步阅读的好东西是Emmanuel Goossaert 6-part guide to coding for SSDs 和论文Don't Stack your Log on my Log [pdf]

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() 到内核页面缓存并让操作系统处理写出的东西。首先,您需要在调用pwrite() 之后再发出一个sync_file_range() 调用才能真正启动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 页面的数量?

【问题讨论】:

  • 我对同样的问题感兴趣,但适用于 iOS 设备。
  • 你的问题不清楚,你关心什么,节省空间,最大化日志?您对 SSD 有 root 访问权限吗?

标签: c++ linux filesystems solid-state-drive


【解决方案1】:

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

简答

您只能在闪存中写入 512 字节的整页。鉴于闪存的写入计数较差,驱动芯片正在缓冲/随机化以提高驱动器寿命。

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

流程可以概括为:

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

长答案

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

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

关于集群,这是在与文件系统相关的更高级别处理的,并且您可以控制。通常在格式化新硬盘时可以选择簇大小,在windows上是指格式化窗口的Allocation Unit Size。

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

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

另一方面,如果你只存储大文件并且你想选择最大的扇区大小,就比你的文件大小略大。

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


实施

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

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

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

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

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

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

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

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


其他

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

【讨论】:

    猜你喜欢
    • 2015-01-29
    • 2021-09-08
    • 2011-05-16
    • 2019-11-26
    • 2012-10-22
    • 1970-01-01
    • 2015-08-04
    • 1970-01-01
    • 2022-01-03
    相关资源
    最近更新 更多