c++ - 写入 SSD 上仅附加文件的最佳方式

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

我想知道登录到 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 数据结构),以便该页面的逻辑地址正如操作系统所理解的,现在指向新的物理页面。因为文件数据在同一个删除块(可以删除的最小页面组)中也不再连续,我们也正在建立一种形式的碎片债务,这将使我们在 future 的 SSD 垃圾收集中付出代价。效率极低。

As an asside, looking at my PC filesystem: C:\WINDOWS\system32>fsutil fsinfo ntfsinfo c: It has a 512 byte sector size and a 4kb allocation (cluster) size. Neither of which map to the SSD page size - probably not very efficient.



仅使用例如编写存在一些问题pwrite()到内核​​页面缓存并让操作系统处理写出的内容。首先,您需要发出额外的 sync_file_range()打后打pwrite()实际启动IO,否则它会一直等到你打电话fsync()并引发 IO Storm 。其次fsync() seems to block future 调用 write()在同一个文件上。最后,您无法控制内核如何将内容写入 SSD,它可能做得很好,也可能做得很差,导致大量写入放大。

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

据我了解,O_DIRECT 要求所有写入都与扇区大小和扇区总数对齐。因此,每次我决定向日志发出附加信息时,我都需要在末尾添加一些填充以使其达到整数个扇区(如果所有写入始终是整数个扇区,它们也将正确对齐,至少在我的代码中。)好吧,那还不错。但我的问题是,将整个 SSD 页面而不是扇区四舍五入不是更好吗?大概这会消除写放大?

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

最佳答案

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

简答

您只能在闪存中写入 512 字节的完整页面。鉴于闪存的写入次数很少,驱动器芯片正在缓冲/随机化以提高驱动器生命周期。

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

该过程可以概括为:

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


  • 长答案

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

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

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

    Fat 32 format

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

    FAT32

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

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

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

    执行

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

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

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

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

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

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

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

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

    其他

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

    关于c++ - 写入 SSD 上仅附加文件的最佳方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36815975/

    相关文章:

    c++ - std::multimap::equal_range 是否有可能返回不正确的结果?

    c++ - ROS 订阅回调 - 使用 boost::bind 和成员函数

    linux - 易于获取 : How to bypass pressing ENTER

    linux - Linux 文件系统上的 vfs_rename

    c# - ZMQ recv 随机拾取垃圾数据 有谁知道为什么?

    java - 如何将 Telegram 联系人照片同步到 Android 联系人?

    linux - EtherCat 模块 :How to communicate with linux

    regex - Bash:在引用表达式中对 grep 使用引用变量

    java - 确定 Java 文件系统中的 `.` 和 `..` 是否为 "special names"

    c++ - 我可以使用掩码在 Boost 目录中迭代文件吗?