在 SSD 上写入仅附加文件的最佳方式
Optimal way of writing to append-only files on an SSD
我想知道登录 SSD 的最佳方式是什么。想像数据库日志之类的东西,您在其中编写仅追加,但您还必须 fsync() 每个事务或几个事务以确保应用程序级别的数据持久性。
我将介绍一些有关 SSD 工作原理的背景知识,所以如果您已经了解所有这些内容,请浏览一下,以防我有什么地方错了。一些值得进一步阅读的好东西是 Emmanuel Goossaert 6-part guide to coding for SSDs and the paper 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 数据结构)以便该页面的逻辑地址正如所理解的 OS 现在指向新的物理页面。因为文件数据在同一个擦除块(可擦除的最小页面组)中也不再连续,我们也在建立一种碎片债务形式,这将在未来 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()
到内核页面缓存并让 OS 处理写出的东西。首先,您需要在调用 pwrite()
之后发出额外的 sync_file_range()
调用以实际启动 IO,否则它将一直等到您调用 fsync()
并引发 IO 风暴。其次 fsync()
seems to block 未来对同一文件的 write()
调用。最后,您无法控制内核如何将内容写入 SSD,它可能做得很好,也可能做得不好,导致大量写入放大。
由于上述原因,并且因为无论如何我都需要 AIO 来读取日志,所以我选择使用 O_DIRECT 和 O_DSYNC 写入日志并拥有完全控制权。
据我了解,O_DIRECT 要求所有写入都与扇区大小和扇区的整数对齐。所以每次我决定向日志发出追加时,我需要在末尾添加一些填充以使其达到整数扇区(如果所有写入始终是整数扇区,它们也将正确对齐,至少在我的代码中。)好的,这还不错。但我的问题是,将 SSD 页而不是扇区四舍五入不是更好吗?大概这会消除写入放大?
这可能会消耗大量 space,尤其是在一次向日志中写入少量数据时(例如几百个字节)。这也可能是不必要的。像三星 EVO 这样的 SSD 有一个写缓存,它们不会在 fsync() 上刷新它。相反,他们依靠电容器在断电时将缓存写入 SSD。在那种情况下,SSD 可能会做正确的事情,一次只将一个附加日志写入扇区——它可能不会写出最后的部分页面,直到下一个附加文件到达并完成它(或者除非它被强制退出由于大量不相关的缓存 IOs。)由于这个问题的答案可能因设备和文件系统而异,有没有一种方法可以编码这两种可能性并测试我的理论? Linux?
上的 updated/RMW 页数的一些测量写入放大的方法
我会尽力回答你的问题,因为我有同样的任务,但在 SD 卡中,它仍然是闪存。
简答
您只能在闪存中写入整页 512 字节。鉴于闪存的写入次数较差,驱动芯片 buffering/randomizing 以提高您的驱动器寿命。
要在闪存中写入一点,您必须擦除它最先所在的整个页面(512 字节)。所以如果你想在某处附加或修改 1 个字节,首先它必须擦除它所在的整个页面。
过程可以概括为:
- 将整个页面读入缓冲区
- 用您添加的内容修改缓冲区
- 擦除整页
- 用修改后的缓冲区重写整个页面
长答案
扇区(页面)基本上是闪存实现和闪存物理驱动程序的硬件,您无法控制。每次更改内容时都必须清除并重写该页面。
您可能已经知道,如果不清除并重写整个 512 字节,就无法重写页面中的单个位。现在,闪存驱动器在扇区损坏之前的写入周期寿命约为 100,000 次。为了提高寿命,通常是物理驱动,有时系统会有一个写入随机算法来避免总是写入同一个扇区。 (顺便说一下,永远不要对 SSD 进行碎片整理;它没有用,最多会缩短使用寿命)。
关于集群,这是在与文件系统相关的更高级别处理的,并且您可以控制。通常,当你格式化一个新的硬盘时,你可以select cluster size,在windows上指的是格式window.
的Allocation Unit Size
据我所知,大多数文件系统都使用位于磁盘开头的索引。该索引将跟踪每个集群以及分配给它的内容。这意味着一个文件将至少占用 1 个扇区,即使它要小得多。
现在的权衡是你的扇区大小更小,你的索引 table 会更大,并且会占用很多 space。但是如果你有很多小文件,那么你会有更好的职业space.
另一方面,如果您只存储大文件并且想要 select 最大的扇区大小,只需略高于您的文件大小即可。
由于您的任务是执行日志记录,我建议您登录单个扇区大小很大的文件。尝试过这种类型的日志后,单个文件夹中包含大量文件可能会导致问题,尤其是当您使用嵌入式设备时。
实施
现在,如果您对驱动器具有原始访问权限并且想要真正优化,您可以直接写入磁盘而不使用文件系统。
好的方面
* 会为你节省不少磁盘 space
*如果您的设计足够聪明,将在发生故障时使磁盘具有容错性
* 如果您使用的是有限系统,将需要更少的资源
不利的一面
*更多的工作和调试
* 驱动器不会被系统本身识别。
如果你只记录日志,你不需要文件系统,你只需要一个入口点到一个页面,在那里写入你的数据,它会不断增加。
我在SD卡上的实现是在flash开始时保存100页来存储读写位置信息。这是在单个页面中保存的,但为了避免内存循环问题,我会在 100 页上以循环方法顺序写入,然后有一个算法来检查哪一个是最后包含最新信息的。
每 5 分钟左右写入一次位置存储,这意味着在断电的情况下我只会丢失 5 分钟的日志。也可以从上次写入位置开始检查其他扇区是否包含有效数据,然后再进一步写入。
这提供了一个非常强大的解决方案,因为它们不太可能 table 损坏。
我也建议缓冲512字节,一页一页写。
其他
您可能还想检查一些特定于日志的文件系统,它们可能只是为您完成这项工作:Log-structured file system
我想知道登录 SSD 的最佳方式是什么。想像数据库日志之类的东西,您在其中编写仅追加,但您还必须 fsync() 每个事务或几个事务以确保应用程序级别的数据持久性。
我将介绍一些有关 SSD 工作原理的背景知识,所以如果您已经了解所有这些内容,请浏览一下,以防我有什么地方错了。一些值得进一步阅读的好东西是 Emmanuel Goossaert 6-part guide to coding for SSDs and the paper 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 数据结构)以便该页面的逻辑地址正如所理解的 OS 现在指向新的物理页面。因为文件数据在同一个擦除块(可擦除的最小页面组)中也不再连续,我们也在建立一种碎片债务形式,这将在未来 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()
到内核页面缓存并让 OS 处理写出的东西。首先,您需要在调用 pwrite()
之后发出额外的 sync_file_range()
调用以实际启动 IO,否则它将一直等到您调用 fsync()
并引发 IO 风暴。其次 fsync()
seems to block 未来对同一文件的 write()
调用。最后,您无法控制内核如何将内容写入 SSD,它可能做得很好,也可能做得不好,导致大量写入放大。
由于上述原因,并且因为无论如何我都需要 AIO 来读取日志,所以我选择使用 O_DIRECT 和 O_DSYNC 写入日志并拥有完全控制权。
据我了解,O_DIRECT 要求所有写入都与扇区大小和扇区的整数对齐。所以每次我决定向日志发出追加时,我需要在末尾添加一些填充以使其达到整数扇区(如果所有写入始终是整数扇区,它们也将正确对齐,至少在我的代码中。)好的,这还不错。但我的问题是,将 SSD 页而不是扇区四舍五入不是更好吗?大概这会消除写入放大?
这可能会消耗大量 space,尤其是在一次向日志中写入少量数据时(例如几百个字节)。这也可能是不必要的。像三星 EVO 这样的 SSD 有一个写缓存,它们不会在 fsync() 上刷新它。相反,他们依靠电容器在断电时将缓存写入 SSD。在那种情况下,SSD 可能会做正确的事情,一次只将一个附加日志写入扇区——它可能不会写出最后的部分页面,直到下一个附加文件到达并完成它(或者除非它被强制退出由于大量不相关的缓存 IOs。)由于这个问题的答案可能因设备和文件系统而异,有没有一种方法可以编码这两种可能性并测试我的理论? Linux?
上的 updated/RMW 页数的一些测量写入放大的方法我会尽力回答你的问题,因为我有同样的任务,但在 SD 卡中,它仍然是闪存。
简答
您只能在闪存中写入整页 512 字节。鉴于闪存的写入次数较差,驱动芯片 buffering/randomizing 以提高您的驱动器寿命。
要在闪存中写入一点,您必须擦除它最先所在的整个页面(512 字节)。所以如果你想在某处附加或修改 1 个字节,首先它必须擦除它所在的整个页面。
过程可以概括为:
- 将整个页面读入缓冲区
- 用您添加的内容修改缓冲区
- 擦除整页
- 用修改后的缓冲区重写整个页面
长答案
扇区(页面)基本上是闪存实现和闪存物理驱动程序的硬件,您无法控制。每次更改内容时都必须清除并重写该页面。
您可能已经知道,如果不清除并重写整个 512 字节,就无法重写页面中的单个位。现在,闪存驱动器在扇区损坏之前的写入周期寿命约为 100,000 次。为了提高寿命,通常是物理驱动,有时系统会有一个写入随机算法来避免总是写入同一个扇区。 (顺便说一下,永远不要对 SSD 进行碎片整理;它没有用,最多会缩短使用寿命)。
关于集群,这是在与文件系统相关的更高级别处理的,并且您可以控制。通常,当你格式化一个新的硬盘时,你可以select cluster size,在windows上指的是格式window.
的Allocation Unit Size据我所知,大多数文件系统都使用位于磁盘开头的索引。该索引将跟踪每个集群以及分配给它的内容。这意味着一个文件将至少占用 1 个扇区,即使它要小得多。
现在的权衡是你的扇区大小更小,你的索引 table 会更大,并且会占用很多 space。但是如果你有很多小文件,那么你会有更好的职业space.
另一方面,如果您只存储大文件并且想要 select 最大的扇区大小,只需略高于您的文件大小即可。
由于您的任务是执行日志记录,我建议您登录单个扇区大小很大的文件。尝试过这种类型的日志后,单个文件夹中包含大量文件可能会导致问题,尤其是当您使用嵌入式设备时。
实施
现在,如果您对驱动器具有原始访问权限并且想要真正优化,您可以直接写入磁盘而不使用文件系统。
好的方面 * 会为你节省不少磁盘 space *如果您的设计足够聪明,将在发生故障时使磁盘具有容错性 * 如果您使用的是有限系统,将需要更少的资源
不利的一面 *更多的工作和调试 * 驱动器不会被系统本身识别。
如果你只记录日志,你不需要文件系统,你只需要一个入口点到一个页面,在那里写入你的数据,它会不断增加。
我在SD卡上的实现是在flash开始时保存100页来存储读写位置信息。这是在单个页面中保存的,但为了避免内存循环问题,我会在 100 页上以循环方法顺序写入,然后有一个算法来检查哪一个是最后包含最新信息的。
每 5 分钟左右写入一次位置存储,这意味着在断电的情况下我只会丢失 5 分钟的日志。也可以从上次写入位置开始检查其他扇区是否包含有效数据,然后再进一步写入。
这提供了一个非常强大的解决方案,因为它们不太可能 table 损坏。
我也建议缓冲512字节,一页一页写。
其他
您可能还想检查一些特定于日志的文件系统,它们可能只是为您完成这项工作:Log-structured file system