Changman Lee, Dongho Sim, Joo-Young Hwang, and Sangyeun Cho
软件开发团队,存储事业部,三星电子
原文发表于第 13 届 USENIX 文件与存储技术会议(FAST '15),2015 年 2 月 16–19 日,美国加利福尼亚州圣克拉拉。
摘要
F2FS 是一个专为现代闪存存储设备设计的 Linux 文件系统。该文件系统基于追加式日志(append-only logging)构建,其核心设计决策充分考虑了闪存存储的特性。本文描述了 F2FS 的主要设计思想、数据结构、算法及其性能表现。实验结果表明,F2FS 具有出色的性能:在最新的移动系统上,其在合成工作负载下的性能最高可达 EXT4 的 3.1 倍(iozone)和 2 倍(SQLite),并将多个真实工作负载的执行时间降低了高达 40%。在服务器系统上,F2FS 的性能最高可达 EXT4 的 2.5 倍(SATA SSD)和 1.8 倍(PCIe SSD)。
1 引言
NAND 闪存已广泛应用于智能手机、平板电脑和 MP3 播放器等各类移动设备中。此外,服务器系统也开始将闪存设备作为主要存储介质。尽管应用广泛,闪存仍存在若干固有限制,包括先擦后写(erase-before-write)要求、需在已擦除块上顺序写入以及有限的擦写寿命。
早期,许多消费电子设备直接使用搭载在平台上的"裸" NAND 闪存。然而,随着存储需求的增长,越来越多的设备开始采用由专用控制器连接多块闪存芯片的"解决方案"。运行在控制器上的固件,即通常所说的 FTL(Flash Translation Layer,闪存转换层),负责处理 NAND 闪存的各种限制并提供通用块设备抽象。此类闪存存储解决方案包括 eMMC(嵌入式多媒体卡)、UFS(通用闪存存储)和 SSD(固态硬盘)。通常,这些现代闪存存储设备的访问延迟远低于其机械对应物——硬盘驱动器(HDD)。在随机 I/O 方面,SSD 的性能更是比 HDD 高出数个数量级。
然而,在闪存存储设备的某些使用场景下,NAND 闪存介质的特异性会显现出来。例如,Min 等人 [21] 观察到,对 SSD 的频繁随机写入会导致底层介质的内部碎片化,从而降低 SSD 的持续性能。研究表明,随机写入模式相当普遍,在资源受限的移动设备闪存方案上更为严峻。Kim 等人 [12] 量化发现,Facebook 移动应用程序的随机写入量比顺序写入多 150%,WebBench 则多出 70%。此外,超过 80% 的总 I/O 为随机操作,其中 70% 以上的随机写入是由 Facebook 和 Twitter 等应用通过 fsync 触发的 [8]。这种特定的 I/O 模式源于这些应用中 SQLite [2] 的主导性使用。如果处理不当,现代工作负载中频繁的随机写入和刷新操作会严重增加闪存设备的 I/O 延迟并缩短设备寿命。
随机写入的不利影响可以通过日志结构文件系统(Log-Structured File System, LFS)方法 [27] 和/或写时复制(Copy-on-Write)策略来减轻。例如,人们可能预期 BTRFS [26] 和 NILFS2 [15] 等文件系统在 NAND 闪存 SSD 上表现良好;不幸的是,它们并未考虑闪存存储设备的特性,在性能和设备寿命方面不可避免地存在不足。我们认为,传统面向 HDD 的文件系统设计策略——虽然有其价值——无法充分利用和优化 NAND 闪存介质的使用。
本文提出了 F2FS 的设计与实现,这是一个专为现代闪存存储设备优化的新型文件系统。据我们所知,F2FS 是第一个公开可用且被广泛使用的文件系统,它从零开始设计,旨在优化具有通用块接口的闪存设备的性能和寿命。[1] 本文描述了其设计与实现。
以下列出了 F2FS 设计的主要考量:
-
闪存友好的磁盘布局(第 2.1 节)。 F2FS 引入了三个可配置单元:段(segment)、节(section)和区(zone)。它以段为单位从多个独立的区中分配存储块,以节为单位执行"清理"(cleaning)操作。引入这些单元是为了与底层 FTL 的操作单元对齐,从而避免不必要且代价高昂的数据复制。
-
高效的索引结构(第 2.2 节)。 LFS 将数据和索引块写入新分配的空闲空间。当一个叶数据块被更新(并写入新位置)时,其直接索引块也必须更新。直接索引块一旦写入,其间接索引块又必须更新。这种递归更新导致一系列写操作链,产生"游走树"(wandering tree)问题 [4]。为了解决这一问题,我们提出了一种新型索引表——节点地址表(Node Address Table, NAT)。
-
多头日志(第 2.4 节)。 我们设计了一种有效的冷热数据分离方案,在日志写入时(即块分配时)应用。该方案同时运行多个活跃日志段,根据数据和元数据的预期更新频率将其追加到不同的日志段中。由于闪存存储设备利用介质并行性,多个活跃段可以同时运行而不需要频繁的管理操作,使得多头日志(相对于单段日志)造成的性能下降微乎其微。
-
自适应日志(第 2.6 节)。 F2FS 基本上基于追加式日志将随机写入转换为顺序写入。然而在高存储利用率下,它会将日志策略切换为线程化日志(threaded logging)[23],以避免长写入延迟。本质上,线程化日志将新数据写入脏段(dirty segment)中的空闲空间,而无需在前台执行清理。这一策略在现代闪存设备上表现良好,但在 HDD 上可能效果不佳。
-
通过前滚恢复加速 fsync(第 2.7 节)。 F2FS 通过最小化所需的元数据写入,并使用高效的前滚恢复机制来恢复已同步的数据,从而优化小型同步写入以降低 fsync 请求的延迟。
简而言之,F2FS 建立在 LFS 概念之上,但通过新的设计考量与原始 LFS 方案有着显著不同。我们将 F2FS 实现为 Linux 文件系统,并与两个先进的 Linux 文件系统——EXT4 和 BTRFS 进行比较。我们还评估了 NILFS2,一个 LFS 在 Linux 中的替代实现。我们的评估考虑了两类目标系统:移动系统和服务器系统。在服务器系统的情况下,我们研究了 SATA SSD 和 PCIe SSD 上的文件系统表现。本文所得到并呈现的结果突显了 F2FS 整体优异的性能特征。
本文余下部分的组织如下:第 2 节描述 F2FS 的设计与实现;第 3 节提供性能结果与讨论;第 4 节介绍相关工作;第 5 节总结全文。
2 F2FS 的设计与实现
2.1 磁盘布局
F2FS 的磁盘数据结构经过精心布局,以匹配底层 NAND 闪存的组织和管理方式。如图 1 所示,F2FS 将整个卷划分为固定大小的段(segment)。段是 F2FS 中的基本管理单元,用于确定初始文件系统元数据布局。一个节(section)由连续的段组成,一个区(zone)由一系列节组成。这些单元在日志写入和清理过程中非常重要,将在第 2.4 节和第 2.5 节进一步讨论。
F2FS 将整个卷划分为六个区域:
-
超级块(Superblock, SB) 包含 F2FS 的基本分区信息和默认参数,在格式化时给定且不可更改。
-
检查点(Checkpoint, CP) 保存文件系统状态、有效 NAT/SIT 集合的位图(见下文)、孤儿 inode 列表以及当前活跃段的摘要条目。一个成功的"检查点包"(checkpoint pack)应存储给定时间点上一致的 F2FS 状态——作为突然掉电事件后的恢复点(第 2.7 节)。CP 区域在两个段(#0 和 #1)上交替存储两个检查点包:一个用于最新的稳定版本,另一个用于中间(过时)版本。
-
段信息表(Segment Information Table, SIT) 包含每段的信息,如有效块数量以及"主区域"(见下文)中所有块的有效性位图。SIT 信息在清理过程中被检索,用于选择受害段(victim segment)并识别其中的有效块(第 2.5 节)。
-
节点地址表(Node Address Table, NAT) 是一个块地址表,用于定位存储在主区域中的所有"节点块"(node block)。
-
段摘要区(Segment Summary Area, SSA) 存储摘要条目,表示主区域中所有块的所有者信息,如父 inode 编号及其节点/数据偏移量。SSA 条目在清理过程中用于在迁移有效块之前识别父节点块。
-
主区域(Main Area) 由 4KB 大小的块填充。每个块被分配并标记为节点(node)或数据(data)类型。节点块包含 inode 或数据块的索引,而数据块包含目录数据或用户文件数据。值得注意的是,一个节(section)不会同时存储数据块和节点块。
基于上述磁盘数据结构,我们来说明文件查找操作的执行过程。假设要查找文件 /dir/file,F2FS 执行以下步骤:(1) 通过 NAT 获取根 inode 的物理位置并读取该块;(2) 在根 inode 块中,从其数据块中搜索名为 dir 的目录条目并获取其 inode 编号;(3) 通过 NAT 将获取到的 inode 编号转换为物理位置;(4) 读取对应块以获取名为 dir 的 inode;(5) 在 dir 的 inode 中,识别名为 file 的目录条目,最终通过对 file 重复步骤 (3) 和 (4) 获取文件 inode。实际数据可通过对应文件结构获取的索引从主区域中检索。
2.2 文件结构
原始 LFS 引入了 inode map 来将 inode 编号转换为磁盘位置。相比之下,F2FS 使用"节点"(node)结构来扩展 inode map,以定位更多的索引块。每个节点块都有一个唯一标识号——"节点 ID"(node ID)。NAT 以节点 ID 为索引,提供所有节点块的物理位置。节点块代表三种类型之一:inode、直接节点(direct node)和间接节点(indirect node)。Inode 块包含文件的元数据,如文件名、inode 编号、文件大小、atime 和 dtime。直接节点块包含数据块的地址,间接节点块则包含指向其他节点块的节点 ID。
如图 2 所示,F2FS 使用基于指针的文件索引,结合直接和间接节点块来消除更新传播(即"游走树"问题 [27])。在传统 LFS 设计中,如果一个叶数据被更新,其直接和间接指针块会被递归更新。而 F2FS 仅更新一个直接节点块及其 NAT 条目,有效地解决了游走树问题。例如,当向一个大小为 8MB 到 4GB 的文件追加 4KB 数据时,LFS 需要递归更新两个指针块,而 F2FS 仅更新一个直接节点块(不考虑缓存效果)。对于大于 4GB 的文件,LFS 需要多更新一个指针块(共三个),而 F2FS 仍然只需更新一个。
inode 块包含指向文件数据块的直接指针、两个一级间接(single-indirect)指针、两个二级间接(double-indirect)指针和一个三级间接(triple-indirect)指针。F2FS 支持内联数据(inline data)和内联扩展属性(inline xattrs),将小尺寸数据或扩展属性直接嵌入 inode 块本身。内联化减少了空间需求并提升了 I/O 性能。值得注意的是,许多系统中存在大量小文件和少量扩展属性。默认情况下,如果文件大小小于 3,692 字节,F2FS 会激活数据内联化。F2FS 在 inode 块中预留 200 字节用于存储扩展属性。
2.3 目录结构
在 F2FS 中,一个 4KB 的目录条目(dentry)块由一个位图和两个成对的槽位(slot)与名称数组组成。位图指示每个槽位是否有效。一个槽位包含哈希值、inode 编号、文件名长度和文件类型(如普通文件、目录和符号链接)。目录文件构建多级哈希表来高效管理大量目录条目。
当 F2FS 在目录中查找给定文件名时,首先计算该文件名的哈希值,然后从第 0 级到 inode 中记录的最大已分配级别逐级遍历构建的哈希表。在每个级别中,它扫描包含两个或四个 dentry 块的一个桶,查找复杂度为 O(log(目录条目数))。为了更快地找到目录条目,F2FS 依次比较位图、哈希值和文件名。
当偏好使用大目录时(例如在服务器环境中),用户可以配置 F2FS 预先为大量目录条目分配空间。通过在低级别使用更大的哈希表,F2FS 可以更快地到达目标目录条目。
2.4 多头日志
与 LFS 使用单一大型日志区域不同,F2FS 维护六个主要日志区域,以最大化冷热数据分离的效果。F2FS 为节点块和数据块静态定义了三个温度级别——热(hot)、温(warm)和冷(cold),如表 1 所总结。
直接节点块被认为比间接节点块更"热",因为它们的更新频率要高得多。间接节点块包含节点 ID,仅在专用节点块被添加或删除时才会被写入。目录的直接节点块和数据块被视为热数据,因为它们与普通文件的块具有明显不同的写入模式。满足以下三个条件之一的数据块被视为冷数据:
- 被清理操作迁移的数据块(见第 2.5 节)。由于这些块在较长时间内保持有效,我们预期它们在近期仍将保持有效。
- 被用户标记为"冷"的数据块。 F2FS 为此提供了扩展属性操作。
- 多媒体文件数据。 它们通常表现为一次写入、多次读取的模式。F2FS 通过将文件扩展名与已注册的文件扩展名列表进行匹配来识别它们。
默认情况下,F2FS 同时开启六路日志进行写入。如果用户认为在特定存储设备和平台上效果更好,可以在挂载时将写入流的数量调整为两路或四路。使用六路日志时,每个日志段直接对应表 1 中列出的一个温度级别。使用四路日志时,F2FS 在节点和数据类型中分别合并冷日志和温日志。仅使用两路日志时,F2FS 分别为节点和数据类型各分配一路。第 3.2.3 节研究了日志头数量对数据分离效果的影响。
F2FS 引入可配置的区(zone)以兼容 FTL,旨在减轻垃圾回收(Garbage Collection, GC)开销。[2] FTL 算法根据数据与"日志闪存块"之间的关联性,大致分为三类:块关联(block-associative)、集合关联(set-associative)和全关联(fully-associative)[24]。一旦数据闪存块被分配用于存储初始数据,日志闪存块便尽可能吸收数据更新,类似于 EXT4 中的日志 [18]。日志闪存块可以专用于单个数据闪存块(块关联)[13]、所有数据闪存块(全关联)[17],或一组连续数据闪存块(集合关联)[24]。现代 FTL 采用全关联或集合关联方法,以便妥善处理随机写入。需要注意的是,F2FS 使用多头日志并行写入节点块和数据块,而关联式 FTL 可能会将文件系统层面已分离的块混合到同一闪存块中。为避免这种错位,F2FS 将活跃日志映射到不同的区,以在 FTL 层面实现分离。该策略对集合关联式 FTL 预期有效。多头日志也与最近提出的"多流"(multi-streaming)接口 [10] 天然匹配。
2.5 清理
清理(Cleaning)是回收分散的无效块并确保有空闲段可供后续日志写入的过程。由于一旦底层存储容量被填满,清理就会持续发生,因此限制与清理相关的开销对于 F2FS(以及任何 LFS)的持续性能至关重要。在 F2FS 中,清理以节(section)为单位执行。
F2FS 以两种不同的方式执行清理:前台清理和后台清理。前台清理仅在空闲节数量不足时触发,而内核线程会定期唤醒以在后台执行清理。清理过程分为三个步骤:
(1) 受害者选择。 清理过程首先从非空节中识别一个受害节(victim section)。LFS 清理中有两种著名的受害者选择策略——贪心策略(greedy)和代价-收益策略(cost-benefit)[11, 27]。贪心策略选择有效块数量最少的节。直观上,该策略控制了迁移有效块的开销。F2FS 在前台清理中采用贪心策略,以最小化对应用程序可见的延迟。此外,F2FS 预留少量未使用容量(默认为存储空间的 5%),以便清理过程在高存储利用率下仍有足够的操作空间。第 3.2.4 节研究了利用率水平对清理成本的影响。另一方面,F2FS 在后台清理中采用代价-收益策略。该策略不仅基于节的利用率,还考虑其"年龄"来选择受害节。F2FS 通过对节中各段的年龄求平均来推断节的年龄,段的年龄则可以从 SIT 中记录的最后修改时间获取。通过代价-收益策略,F2FS 获得了又一次分离冷热数据的机会。
(2) 有效块识别与迁移。 选定受害节后,F2FS 必须快速识别节中的有效块。为此,F2FS 在 SIT 中维护每段的有效性位图。扫描位图识别出所有有效块后,F2FS 从 SSA 信息中检索包含其索引的父节点块。如果这些块有效,F2FS 将它们迁移到其他空闲日志。
对于后台清理,F2FS 不会发出实际的 I/O 来迁移有效块。相反,F2FS 将块加载到页缓存(page cache)中并标记为脏。然后,F2FS 将它们留在页缓存中,由内核工作线程稍后将其刷写到存储设备。这种延迟迁移不仅减轻了对前台 I/O 活动的性能影响,还允许小写入操作被合并。当正常 I/O 或前台清理正在进行时,后台清理不会启动。
(3) 清理后处理。 所有有效块迁移完成后,受害节被注册为候选的新空闲节(在 F2FS 中称为"预空闲"(pre-free)节)。在检查点完成后,该节最终成为空闲节,可被重新分配。之所以这样做,是因为如果预空闲节在检查点之前被重用,当意外断电发生时,文件系统可能会丢失前一个检查点引用的数据。
2.6 自适应日志
原始 LFS 引入了两种日志策略:普通日志(normal logging)和线程化日志(threaded logging)。在普通日志模式下,块被写入干净的段,产生严格的顺序写入。即使用户提交大量随机写入请求,只要存在足够的空闲日志空间,该过程就能将它们转换为顺序写入。然而,当空闲空间缩减至零时,该策略开始承受高昂的清理开销,导致严重的性能下降(在苛刻条件下量化为超过 90%,见第 3.2.5 节)。另一方面,线程化日志将块写入现有脏段中的空洞(已失效的过时空间)。该策略不需要清理操作,但会触发随机写入,可能因此降低性能。
F2FS 实现了这两种策略,并根据文件系统状态在它们之间动态切换。具体而言,如果存在超过 k 个干净节(clean section),其中 k 是一个预定义阈值,则启用普通日志。否则,激活线程化日志。k 默认设置为总节数的 5%,可以配置。
线程化日志在存在分散空洞时可能引发不理想的随机写入。然而,与原地更新(update-in-place)文件系统相比,这种随机写入通常表现出更好的空间局部性,因为 F2FS 会先填满一个脏段中的所有空洞,然后再去其他脏段中寻找更多空洞。Lee 等人 [16] 证明,闪存存储设备在具有强空间局部性的情况下展现出更好的随机写入性能。F2FS 优雅地放弃普通日志并转向线程化日志以获得更高的持续性能,这将在第 3.2.5 节中展示。
2.7 检查点与恢复
F2FS 实现了检查点机制,以提供突然断电或系统崩溃后的一致恢复点。每当需要跨事件(如 sync、umount 和前台清理)保持一致状态时,F2FS 按以下步骤触发检查点过程:(1) 将页缓存中所有脏节点块和脏目录条目块刷写;(2) 暂停常规写入活动,包括 create、unlink 和 mkdir 等系统调用;(3) 将文件系统元数据——NAT、SIT 和 SSA——写入磁盘上的专用区域;(4) 最后,F2FS 将一个检查点包写入 CP 区域,包含以下信息:
- 头部和尾部(Header and Footer) 分别写在包的开头和末尾。F2FS 在头部和尾部维护一个版本号,每次创建检查点时递增。该版本号用于在挂载时区分两个已记录包中的最新稳定包。
- NAT 和 SIT 位图 指示构成当前包的 NAT 和 SIT 块集合。
- NAT 和 SIT 日志 包含少量最近修改的 NAT 和 SIT 条目,以避免频繁的 NAT 和 SIT 更新。
- 活跃段的摘要块 由内存中的 SSA 块组成,将在未来刷写到 SSA 区域。
- 孤儿块(Orphan blocks) 保存"孤儿 inode"信息。如果一个 inode 在关闭之前被删除(例如,当两个进程打开同一个文件而其中一个进程将其删除时),它应被注册为孤儿 inode,以便 F2FS 在突然断电后能够恢复它。
2.7.1 回滚恢复
在突然断电之后,F2FS 回滚到最新的一致检查点。为了在创建新检查点包的同时至少保持一个稳定的检查点包,F2FS 维护两个检查点包。如果一个检查点包的头部和尾部内容相同,F2FS 认为它是有效的;否则,该包被丢弃。
同样,F2FS 也管理两组 NAT 和 SIT 块,通过每个检查点包中的 NAT 和 SIT 位图加以区分。在检查点过程中写入更新后的 NAT 或 SIT 块时,F2FS 交替写入两组中的一组,然后将位图标记指向新的那组。
如果少量 NAT 或 SIT 条目被频繁更新,F2FS 将需要写入大量 4KB 大小的 NAT 或 SIT 块。为减轻这一开销,F2FS 在检查点包内实现了 NAT 和 SIT 日志。这一技术减少了 I/O 次数,从而也降低了检查点延迟。
在挂载时的恢复过程中,F2FS 通过检查头部和尾部来搜索有效的检查点包。如果两个检查点包都有效,F2FS 通过比较它们的版本号选择最新的一个。选定最新的有效检查点包后,它检查是否存在孤儿 inode 块。如果存在,则截断这些孤儿 inode 引用的所有数据块,最后释放孤儿 inode。然后,在前滚恢复过程成功完成后(如下所述),F2FS 使用由位图引用的一致 NAT 和 SIT 块集合启动文件系统服务。
2.7.2 前滚恢复
数据库等应用(如 SQLite)频繁地向文件写入少量数据并执行 fsync 以保证持久性。支持 fsync 的一种简单方法是触发检查点并使用回滚模型恢复数据。然而,这种方法导致性能低下,因为检查点操作涉及写入与数据库文件无关的所有节点块和目录条目块。
F2FS 实现了高效的前滚恢复(roll-forward recovery)机制来增强 fsync 性能。其核心思想是仅写入数据块及其直接节点块,而排除其他节点块或 F2FS 元数据块。为了在回滚到稳定检查点后能有选择性地找到这些数据块,F2FS 在直接节点块内保留一个特殊标志。
F2FS 按如下方式执行前滚恢复。设最后一个稳定检查点的日志位置为 N,则:(1) F2FS 收集位于 N+n 位置处具有特殊标志的直接节点块,同时构建一个包含其节点信息的列表。n 表示自最后一个检查点以来更新的块数。(2) 利用列表中的节点信息,加载最近写入的节点块(记为 N-n)到页缓存中。(3) 然后,比较 N-n 和 N+n 之间的数据索引。(4) 如果检测到不同的数据索引,则用 N+n 中存储的新索引刷新缓存的节点块,最后将它们标记为脏。完成前滚恢复后,F2FS 执行检查点将所有内存中的变更存储到磁盘。
3 实验评估
3.1 实验设置
我们在两类目标系统——移动系统和服务器系统——上评估 F2FS。我们使用 Galaxy S4 智能手机代表移动系统,使用 x86 平台代表服务器系统。平台规格汇总在表 2 中。
对于目标系统,我们分别将 F2FS 从 3.15-rc1 主线内核回移植(back-port)到 3.4.5 和 3.14 内核。在移动系统中,F2FS 运行在最新的 eMMC 存储上。在服务器系统中,我们使用 SATA SSD 和(更高速的)PCIe SSD。括号中标注的数值表示每个存储设备的基本顺序读/写和随机读/写带宽(单位:MB/s)。我们通过一个简单的单线程应用程序测量带宽,该程序使用 O_DIRECT 标志触发 512KB 顺序 I/O 和 4KB 随机 I/O。
我们将 F2FS 与 EXT4 [18]、BTRFS [26] 和 NILFS2 [15] 进行比较。EXT4 是一个广泛使用的原地更新(update-in-place)文件系统。BTRFS 是一个写时复制(copy-on-write)文件系统,NILFS2 是一个 LFS。
表 3 总结了我们的基准测试及其在生成的 I/O 模式、涉及的文件数量及其最大大小、工作线程数、读写比例(R/W)以及是否有 fsync 系统调用等方面的特征。对于移动系统,我们执行并展示 iozone [22] 的结果,以研究基本文件 I/O 性能。由于移动系统受到频繁 fsync 调用带来的高代价随机写入的影响,我们运行 mobibench [8]——一个宏基准测试——来测量 SQLite 性能。我们还回放了在真实使用场景下从"Facebook"和"Twitter"应用收集的两组系统调用轨迹(分别称为"Facebook-app"和"Twitter-app")[8]。
对于服务器工作负载,我们使用名为 Filebench [20] 的合成基准测试。它模拟各种文件系统工作负载,允许快速直观地评估系统性能。我们使用基准测试中的四个预定义工作负载——videoserver、fileserver、varmail 和 oltp。它们在 I/O 模式和 fsync 使用方面各不相同。
Videoserver 主要发出顺序读写。Fileserver 预分配 80,000 个 128KB 的文件,随后启动 50 个线程,每个线程随机创建和删除文件,以及对随机选择的文件进行读取和追加少量数据。因此,该工作负载代表了一种具有大量大文件、缓冲随机写入且无 fsync 的场景。Varmail 创建和删除大量小文件并配合 fsync,而 oltp 预分配十个大文件并使用 200 个并行线程通过 fsync 随机更新其数据。
3.2 实验结果
本节给出性能结果和从深入块轨迹级分析中获得的见解。我们考察了各种 I/O 模式(即读、写、fsync 和 discard[3])、I/O 量和请求大小分布。为了直观且一致地比较,我们将性能结果归一化到 EXT4 的性能。我们注意到,性能基本取决于顺序 I/O 和随机 I/O 之间的速度差距。在移动系统中,由于计算能力低且存储速度慢,I/O 模式及其数量是主要性能因素。对于服务器系统,CPU 效率(包括指令执行开销和锁竞争)成为额外的关键因素。
3.2.1 移动系统上的性能
图 3(a) 展示了在单个 1GB 文件上进行顺序读/写(SR/SW)和随机读/写(RR/RW)的 iozone 带宽结果。在 SW(顺序写入)情况下,NILFS2 的性能比 EXT4 下降了近 50%,原因是它根据自身的数据刷新策略周期性地触发昂贵的同步写入。在 RW(随机写入)情况下,F2FS 的性能比 EXT4 高出 3.1 倍,因为它将超过 90% 的 4KB 随机写入转换为 512KB 顺序写入(图中未直接显示)。BTRFS 也表现良好(1.8 倍),因为它通过写时复制策略产生顺序写入。虽然 NILFS2 也将随机写入转换为顺序写入,但由于昂贵的同步写入,仅获得了 10% 的提升。此外,它发出的写入请求比其他文件系统多达 30%。对于 RR(随机读取),所有文件系统的性能相当。BTRFS 由于其树索引开销而显示出略低的性能。
图 3(b) 给出了以每秒事务数(TPS)衡量的 SQLite 性能,归一化到 EXT4 的性能。我们在由 1,000 条记录组成的数据库上,采用预写日志(WAL)日志模式测量了三种事务类型——插入(insert)、更新(update)和删除(delete)。WAL 日志模式被认为是 SQLite 中最快的模式。F2FS 显示出明显优于其他文件系统的性能,最高可达 EXT4 的 2 倍。对于这一工作负载,F2FS 的前滚恢复策略带来了巨大的收益。事实上,在所有测试案例中,F2FS 将数据写入量减少了约 46%(相比 EXT4)。由于繁重的索引开销,BTRFS 的数据写入量是 EXT4 的 3 倍,导致性能下降了近 80%。NILFS2 在数据写入量几乎相同的情况下实现了与 EXT4 相似的性能。
图 3(c) 展示了完成回放 Facebook-app 和 Twitter-app 轨迹的归一化耗时。这些应用依赖 SQLite 存储数据,F2FS 相比 EXT4 分别将耗时降低了 20%(Facebook-app)和 40%(Twitter-app)。
3.2.2 服务器系统上的性能
图 4 绘制了在 SATA 和 PCIe SSD 上各文件系统的性能。每个柱形表示归一化性能(即当柱形值大于 1 时表示性能提升)。
Videoserver 主要产生顺序读写,所有结果——无论使用何种设备——在各文件系统之间未显示性能差距。这表明 F2FS 在正常顺序 I/O 上没有性能回退。
Fileserver 具有不同的 I/O 模式;图 5 比较了所有文件系统在 SATA SSD 上获取的块轨迹。仔细检查发现,EXT4 生成的所有写入请求中仅有 0.9% 为 512KB,而 F2FS 为 6.9%(图中未直接显示)。另一个发现是 EXT4 发出了大量小型 discard 命令,导致可见的命令处理开销,尤其是在 SATA 驱动器上;它修剪了数据写入覆盖的三分之二的块地址,且近 60% 的 discard 命令针对小于 256KB 的地址空间。相比之下,F2FS 仅在触发检查点时才以段为单位丢弃过时空间;它修剪了 38% 的块地址空间,且没有小型 discard 命令。这些差异带来了 2.4 倍的性能提升(图 4(a))。
另一方面,BTRFS 性能下降了 8%,因为其所有写入请求中仅 3.8% 为 512KB 数据写入。此外,它在读取服务期间使用小型 discard 命令修剪了 47% 的块地址空间(占所有 discard 命令的 75%),如图 5(c) 所示。在 NILFS2 的情况下,其写入请求中多达 78% 为 512KB(图 5(d))。然而,其周期性的同步数据刷新将相对于 EXT4 的性能提升限制在了 1.8 倍。在 PCIe SSD 上,所有文件系统的表现较为相似,因为本研究中使用的 PCIe SSD 能很好地处理并发缓冲写入。
在 varmail 情况下,F2FS 的性能分别在 SATA SSD 和 PCIe SSD 上超过 EXT4 2.5 倍和 1.8 倍。由于 varmail 生成大量带有并发 fsync 的小写入,该结果再次突显了 F2FS 中 fsync 处理的高效性。BTRFS 的性能与 EXT4 相当,NILFS2 在 PCIe SSD 上表现相对良好。
oltp 工作负载在单个 800MB 数据库文件上生成大量随机写入和 fsync 调用(与 varmail 操作多个小文件不同)。F2FS 相比 EXT4 显示出可测量的性能优势——在 SATA SSD 上提升 16%,在 PCIe SSD 上提升 13%。另一方面,BTRFS 和 NILFS2 在 PCIe 驱动器上表现较差。PCIe 驱动器上快速的命令处理和高效的随机写入似乎转移了性能瓶颈点,BTRFS 和 NILFS2 未能展现出稳健的性能。
以上结果清楚地证明了 F2FS 整体设计与实现的相对有效性。下面我们将考察 F2FS 日志和清理策略的影响。
3.2.3 多头日志效果
本节研究 F2FS 多头日志策略的有效性。我们聚焦于一个能捕捉设计直觉的实验,而非展示跨多种工作负载的大量评估结果。本节使用的度量指标是清理前给定脏段中的有效块数量。如果冷热数据分离做得完美,一个脏段应该要么有零个有效块,要么有段中最大数量的有效块(在默认配置下为 512)。一个老化的脏段,如果其中存储的所有(热)数据都已失效,则会携带零个有效块。相比之下,一个满载有效块的脏段很可能保存着冷数据。
在我们的实验中,我们同时运行两个工作负载:varmail 和复制 jpeg 文件。Varmail 在 100 个目录中共使用 10,000 个文件,写入 6.5GB 数据。我们复制 5,000 个约 500KB 的 jpeg 文件,因此产生 2.5GB 的写入数据。注意,F2FS 将 jpeg 文件静态分类为冷数据。在这些工作负载完成后,我们统计所有脏段中的有效块数量。我们在日志数量从两路到六路变化时重复实验。
图 6 给出了结果。使用两路日志时,超过 75% 的段拥有超过 256 个有效块,而拥有 512 个有效块的"满段"非常少。由于两路日志配置仅分离数据段(占所有脏段的 85%,未显示)和节点段(15%),多头日志的效果相当有限。增加两路日志使情况有所改变;它增加了有效块数少于 256 的段数量,也略微增加了接近满段的数量。
最后,使用六路日志时,我们可以清晰地看到冷热数据分离的收益;拥有零个有效块的预空闲段和满段的数量均显著增加。此外,有效块较少(128 个或更少)的段和有效块较多(384 个或更多)的段也增多了。这种双峰分布的明显影响是提高了清理效率(因为清理成本取决于受害段中有效块的数量)。
在结束本节之前,我们做几个观察。首先,结果表明更多的日志——允许更精细的数据温度分离——通常带来更多收益。然而,在我们进行的特定实验中,四路日志相对于两路日志的收益并不显著。如果我们将冷数据从热数据和温数据中分离(而非按默认方式将热数据从温数据和冷数据中分离),结果将会不同。其次,由于脏段中的有效块数量会随时间逐渐减少,图 6 中曲线最左端的拐点会向上移动(根据所选日志配置的不同速度)。因此,如果我们老化文件系统,我们预期多头日志的收益将更加明显。全面研究这些观察超出了本文的范围。
3.2.4 清理成本
本节量化 F2FS 中清理的影响。为了聚焦于文件系统层面的清理成本,我们通过在 SSD 中预留充足的空闲空间来确保实验期间不发生 SSD 级别的 GC。为此,我们格式化一块 250GB 的 SSD 并获取一个(仅)120GB 的分区。
在预留 5% 的空间用于超额配置(overprovisioning,第 2.5 节)后,我们将剩余容量分为"冷"和"热"区域。我们通过以下方式填充这两个区域来构建四种反映不同文件系统利用率水平的配置:80%(60(冷):20(热))、90%(60:30)、95%(60:35)和 97.5%(60:37.5)。然后,我们迭代十轮实验,每轮以 4KB 粒度向热区域随机写入 20GB 数据。
图 7 绘制了前十轮的结果,使用两个指标:性能(吞吐量)和写放大因子(Write Amplification Factor, WAF)。[4] 它们相对于干净 SSD 上获得的结果。我们做出两个主要观察。第一,更高的文件系统利用率导致更大的 WAF 和更低的性能。在 80% 利用率下,性能下降和 WAF 增加都相当轻微。在第三轮时,文件系统耗尽了空闲段,出现了性能骤降。在此轮中,它从普通日志切换到了线程化日志,性能随之稳定下来。(我们将在第 3.2.5 节重新审视自适应线程化日志的效果。)第三轮之后,几乎所有数据都通过线程化日志原地写入。在这种情况下,清理不是为了数据,而是为了记录节点。随着利用率从 80% 提升到 97.5%,GC 量增加,性能退化也更加明显。在 97.5% 时,性能损失约 30%,WAF 约 1.02。
第二个观察是,F2FS 在高利用率下不会急剧增加 WAF;自适应日志在保持 WAF 低水平方面发挥了重要作用。注意,线程化日志引发随机写入,而普通日志发出顺序写入。虽然随机写入相对昂贵,且这促使许多文件系统将追加式日志作为首选操作模式,但我们的设计选择(切换到线程化日志)是有道理的,因为:当文件系统碎片化时,清理可能因高 WAF 而变得非常昂贵,而 SSD 具有较高的随机写入性能。本节的结果表明,F2FS 在高利用率下成功控制了清理成本。
展示后台清理的积极影响并不简单,因为后台清理在繁忙期间会被抑制。尽管如此,我们在 90% 利用率下,当轮次之间插入十分钟或更长的空闲时间时,测量到了超过 10% 的性能提升。
3.2.5 自适应日志性能
本节深入探讨一个问题:F2FS 结合线程化日志的自适应日志策略有多有效?默认情况下,当空闲节数量降至总节数的 5% 以下时,F2FS 从普通日志切换到线程化日志。我们将此默认配置("F2FS adaptive")与始终坚持普通日志策略的"F2FS normal"进行比较。为实验,我们在 SATA SSD 上设计并执行了以下两个直观测试。
- fileserver 测试。 该测试首先用数百个 1GB 文件将目标存储分区填充至 94%。然后运行 fileserver 工作负载四次,并测量性能趋势(图 8(a))。随着实验的重复,底层闪存存储设备和文件系统都会碎片化。相应地,工作负载的性能预计会下降。注意,我们无法对 NILFS2 执行此测试,因为它以"无空间"错误停止。
EXT4 表现出最温和的性能下降——第一轮和第二轮之间下降 17%。相比之下,BTRFS 和 F2FS(尤其是 F2FS normal)分别出现了 22% 和 48% 的严重性能下降,因为它们找不到足够的顺序空间。另一方面,F2FS adaptive 将总写入量的 51% 通过线程化日志服务(图中未显示),成功将第二轮的性能退化限制在 22%(与 BTRFS 相当,与 EXT4 差距不大)。因此,F2FS 在所有轮次中保持了相对于 EXT4 两倍或以上的性能提升比。所有文件系统在第二轮之后的性能都趋于稳定。
进一步检查发现,由于前台清理,F2FS normal 比 F2FS adaptive 多写入 27% 的数据。BTRFS 的大幅性能下降部分归因于大量小型 discard 命令的使用。
- iozone 测试。 该测试首先创建十六个 4GB 文件和额外的 1GB 文件,直到填满设备容量(约 100%)。然后运行 iozone 对这十六个 4GB 文件执行 4KB 随机写入。每个文件的聚合写入量为 512MB。我们重复此步骤十次,事实证明这相当苛刻,因为 BTRFS 和 NILFS2 都以"无空间"错误失败。注意,从理论角度看,EXT4 作为原地更新文件系统,在此测试中应表现最佳,因为 EXT4 发出随机写入而不创建额外的文件系统元数据。另一方面,像 F2FS 这样的日志结构文件系统可能承受高清理成本。还请注意,此工作负载会碎片化存储设备中的数据,随着工作负载触发反复的设备内部 GC 操作,存储性能将受到影响。
在 EXT4 下,性能退化约 75%(图 8(b))。在 F2FS normal 的情况下,正如预期的那样,从第三轮起性能下降到非常低的水平(不到 EXT4 的 5%),因为文件系统和存储设备都在忙于清理碎片化的容量以回收新的日志空间。F2FS adaptive 被证明能更优雅地处理这种情况;它在前几轮(碎片化不严重时)性能优于 EXT4,并且随着实验推进产生更多随机写入后,其性能与 EXT4 非常相似。
本节的两个实验揭示,自适应日志对于 F2FS 在高存储利用率下维持性能至关重要。自适应日志策略还被证明能有效限制碎片化导致的 F2FS 性能退化。
4 相关工作
本节从三个类别讨论与我们相关的前期工作——日志结构文件系统、面向闪存的文件系统以及 FTL 特定优化。
4.1 日志结构文件系统(LFS)
日志结构文件系统的大量工作始于 Rosenblum 等人 [27] 的原始 LFS 提议。Wilkes 等人提出了一种空洞填充(hole plugging)方法,将受害段的有效块移动到其他脏段中的空洞(即无效块)[30]。Matthews 等人提出了一种自适应清理策略,根据代价-收益评估在普通日志策略和空洞填充策略之间进行选择 [19]。Oh 等人 [23] 证明线程化日志在高利用率卷上提供更好的性能。F2FS 在前期工作和真实工作负载及设备的基础上进行了调优。
许多研究聚焦于冷热数据分离。Wang 和 Hu [28] 提出在缓冲区缓存中区分活跃和非活跃数据,而不是将它们写入单一日志后在清理时才进行分离。他们通过监控访问模式来确定哪些数据是活跃的。Hylog [29] 采用混合方法:对热页面使用日志以实现高随机写入性能,对冷页面使用覆写以降低清理成本。
SFS [21] 是一个基于 NILFS2 实现的 SSD 文件系统。与 F2FS 类似,SFS 使用日志来消除随机写入。为降低清理成本,它们像 [28] 一样在缓冲区缓存中分离冷热数据,基于通过跟踪每个块的写入计数和年龄来测量的"更新可能性"(或热度)。它们使用迭代量化将段划分为基于测量热度的组。
与那些依赖运行时监控访问模式来实现冷热数据分离的方法 [21, 28] 不同,F2FS 使用现有的可用信息来估计更新可能性,如文件操作类型(追加或覆写)、文件类型(目录或普通文件)和文件扩展名。虽然我们的实验结果表明这种简单方法相当有效,但更复杂的运行时监控方法可以被整合到 F2FS 中以精细追踪数据温度。
NVMFS 是一个实验性文件系统,假设存在两种不同的存储介质:NVRAM 和 NAND 闪存 SSD [25]。来自 NVRAM 的快速字节可寻址存储容量用于存储热数据和元数据。此外,对 SSD 的写入与 F2FS 一样被顺序化。
4.2 闪存文件系统
已有许多文件系统被提出和实现,用于使用裸 NAND 闪存作为存储的嵌入式系统 [1, 3, 6, 14, 31]。这些文件系统直接访问 NAND 闪存,同时处理所有芯片级问题,如磨损均衡和坏块管理。与这些系统不同,F2FS 面向配备专用控制器和固件(FTL)来处理底层任务的闪存存储设备。此类闪存存储设备更为普遍。
Josephson 等人提出了直接文件系统(DFS)[9],它利用主机端运行的 FTL 提供的特殊支持——包括原子更新接口和超大逻辑地址空间——来简化文件系统设计。然而,DFS 仅限于特定的闪存设备和系统配置,且非开源。
4.3 FTL 优化
大量工作致力于在 FTL 层面提升随机写入性能,其中一些设计策略与 F2FS 共通。大多数 FTL 使用日志结构更新方法来克服闪存的不可覆写限制。DAC [5] 提供了一种页映射 FTL,通过在运行时监控访问来基于更新频率对数据进行聚类。为减少大型页映射表的开销,DFTL [7] 按需动态加载页映射的一部分到工作内存中,为 RAM 有限的设备提供了页映射的随机写入优势。
混合映射(或日志块映射)是块映射的扩展,用于改善随机写入 [13, 17, 24]。它的映射表比页映射小,而对于具有大量访问局部性的工作负载,其性能可以与页映射相当。
5 总结
F2FS 是一个完整的 Linux 文件系统,专为现代闪存存储设备设计,并有望在行业中获得更广泛的采用。本文描述了 F2FS 的关键设计和实现细节。我们的评估结果突显了我们的设计决策和权衡如何带来相对于其他现有文件系统的性能优势。F2FS 还很年轻——它于 2012 年末被纳入 Linux 内核 3.8。我们预期新的优化和功能将持续被添加到该文件系统中。
致谢
作者感谢审稿人和我们的领航人(shepherd)Ted Ts'o 的建设性意见,这些意见帮助提升了本文的质量。这项工作是长期而专注的团队努力的成果;没有前 F2FS 团队成员的大量贡献(特别是 Jaegeuk Kim、Chul Lee、ByoungGeun Kim、Sehwan Lee、Seokyoung Ko、Dongbin Park 和 Sunghoon Park),这项工作不可能完成。
参考文献
[1] Unsorted block image file system. www.linux-mtd.infradead.org/doc/ubifs.h….
[2] Using databases in android: SQLite. developer.android.com/guide/topic….
[3] Yet another flash file system. www.yaffs.net/.
[4] A. B. Bityutskiy. JFFS3 design issues. www.linux-mtd.infradead.org, 2005.
[5] M.-L. Chiang, P. C. Lee, and R.-C. Chang. Using data clustering to improve cleaning performance for flash memory. Software-Practice and Experience, 29(3):267–290, 1999.
[6] J. Engel and R. Mertens. LogFS-finally a scalable flash file system. In Proceedings of the International Linux System Technology Conference, 2005.
[7] A. Gupta, Y. Kim, and B. Urgaonkar. DFTL: a flash translation layer employing demand-based selective caching of page-level address mappings, volume 44. ACM, 2009.
[8] S. Jeong, K. Lee, S. Lee, S. Son, and Y. Won. I/O stack optimization for smartphones. In Proceedings of the USENIX Annual Technical Conference (ATC), pages 309–320, 2013.
[9] W. K. Josephson, L. A. Bongo, K. Li, and D. Flynn. DFS: A file system for virtualized flash storage. ACM Transactions on Storage (TOS), 6(3):14:1–14:25, 2010.
[10] J.-U. Kang, J. Hyun, H. Maeng, and S. Cho. The multi-streamed solid-state drive. In 6th USENIX Workshop on Hot Topics in Storage and File Systems (HotStorage 14), 2014.
[11] A. Kawaguchi, S. Nishioka, and H. Motoda. A flash-memory based file system. In Proceedings of the USENIX Annual Technical Conference (ATC), pages 155–164, 1995.
[12] H. Kim, N. Agrawal, and C. Ungureanu. Revisiting storage for smartphones. ACM Transactions on Storage (TOS), 8(4):14, 2012.
[13] J. Kim, J. M. Kim, S. H. Noh, S. L. Min, and Y. Cho. A space-efficient flash translation layer for compactflash systems. IEEE Transactions on Consumer Electronics, 48(2):366–375, 2002.
[14] J. Kim, H. Shim, S.-Y. Park, S. Maeng, and J.-S. Kim. Flashlight: A lightweight flash file system for embedded systems. ACM Transactions on Embedded Computing Systems (TECS), 11(1):18, 2012.
[15] R. Konishi, Y. Amagai, K. Sato, H. Hifumi, S. Kihara, and S. Moriai. The Linux implementation of a log-structured file system. ACM SIGOPS Operating Systems Review, 40(3):102–107, 2006.
[16] S. Lee, D. Shin, Y.-J. Kim, and J. Kim. LAST: locality-aware sector translation for NAND flash memory-based storage systems. ACM SIGOPS Operating Systems Review, 42(6):36–42, 2008.
[17] S.-W. Lee, D.-J. Park, T.-S. Chung, D.-H. Lee, S. Park, and H.-J. Song. A log buffer-based flash translation layer using fully-associative sector translation. ACM Transactions on Embedded Computing Systems (TECS), 6(3):18, 2007.
[18] A. Mathur, M. Cao, S. Bhattacharya, A. Dilger, A. Tomas, and L. Vivier. The new ext4 filesystem: current status and future plans. In Proceedings of the Linux Symposium, volume 2, pages 21–33, 2007.
[19] J. N. Matthews, D. Roselli, A. M. Costello, R. Y. Wang, and T. E. Anderson. Improving the performance of log-structured file systems with adaptive methods. In Proceedings of the ACM Symposium on Operating Systems Principles (SOSP), pages 238–251, 1997.
[20] R. McDougall, J. Crase, and S. Debnath. Filebench: File system microbenchmarks. www.opensolaris.org, 2006.
[21] C. Min, K. Kim, H. Cho, S.-W. Lee, and Y. I. Eom. SFS: Random write considered harmful in solid state drives. In Proceedings of the USENIX Conference on File and Storage Technologies (FAST), pages 139–154, 2012.
[22] W. D. Norcott and D. Capps. Iozone filesystem benchmark. URL: www.iozone.org, 55, 2003.
[23] Y. Oh, E. Kim, J. Choi, D. Lee, and S. H. Noh. Optimizations of LFS with slack space recycling and lazy indirect block update. In Proceedings of the Annual Haifa Experimental Systems Conference, page 2, 2010.
[24] C. Park, W. Cheon, J. Kang, K. Roh, W. Cho, and J.-S. Kim. A reconfigurable FTL (flash translation layer) architecture for NAND flash-based applications. ACM Transactions on Embedded Computing Systems (TECS), 7(4):38, 2008.
[25] S. Qiu and A. L. N. Reddy. NVMFS: A hybrid file system for improving random write in NAND-flash SSD. In IEEE 29th Symposium on Mass Storage Systems and Technologies, MSST 2013, pages 1–5, 2013.
[26] O. Rodeh, J. Bacik, and C. Mason. Btrfs: The linux b-tree filesystem. ACM Transactions on Storage (TOS), 9(3):9, 2013.
[27] M. Rosenblum and J. K. Ousterhout. The design and implementation of a log-structured file system. ACM Transactions on Computer Systems (TOCS), 10(1):26–52, 1992.
[28] J. Wang and Y. Hu. WOLF: A novel reordering write buffer to boost the performance of log-structured file systems. In Proceedings of the USENIX Conference on File and Storage Technologies (FAST), pages 47–60, 2002.
[29] W. Wang, Y. Zhao, and R. Bunt. Hylog: A high performance approach to managing disk layout. In Proceedings of the USENIX Conference on File and Storage Technologies (FAST), pages 144–158, 2004.
[30] J. Wilkes, R. Golding, C. Staelin, and T. Sullivan. The HP AutoRAID hierarchical storage system. ACM Transactions on Computer Systems (TOCS), 14(1):108–136, 1996.
[31] D. Woodhouse. JFFS: The journaling flash file system. In Proceedings of the Ottawa Linux Symposium, 2001.