Zoned NameSpaces:将区域引入 NVMe SSD¶
标题:Zoned NameSpaces: Bringing zones to NVMe SSDs
日期:2021/02/03
作者:Aravind Ramesh
链接:https://www.youtube.com/watch?v=yHpDh9M0Tuw
注意:此为 AI 翻译生成 的中文转录稿,详细说明请参阅仓库中的 README 文件。
FIXME:我的错,我对 AI 说可以适度高亮,结果整篇稿子就胡乱加粗了……
大家好,欢迎参加本次演讲,“Zone Namespaces:为NVMe SSD引入分区”。我是Arvind Ramesh,西部数据研究院的首席工程师。那么,今天我将涵盖NVMe ZNS概述、分区设备(Zoned Devices)简介、什么是分区命名空间(Zoned Namespace)和NVMe ZNS规范、TP4053概述、分区存储语义(Zoned Storage Semantics)和分区命名空间命令集(Zone Namespace Command Set)的简要特性、ZNS优势、挑战、用例、F2FS用例和软件栈当前状态、软件栈未来预期工作。这是议程。
是的,那么先简要介绍一下,ZNS设备是分区块设备(zoned block devices)的一个子集。而分区块设备通常指的是那些将介质以分区(zones)形式暴露出来的设备。分区不过是该介质内的逻辑实体。我们有SMR设备,它们是分区设备,但它们是硬盘,是单磁记录设备。因此这些设备也属于分区块设备。那么另一种类型的分区块设备,你可以称之为OCSSD设备。因此它们是开放通道设备,SSD。在那里,设备暴露了其内部NAND芯片的布局。主机可以自由地选择和放置它想要读写数据的位置。然后就是NVMe ZNS设备,它们遵循TP4053规范。今天我们将讨论这个。那么,什么是分区命名空间命令集?
这个TP4053规范于2020年6月发布。NVMe ZNS命令集规范允许主机软件使用分区与SSD通信。它定义了分区存储模型及其属性。所以,如果你看这里的这张图片,如果你假设这是一个完整的SSD,那么它显示从分区0到分区X-1,这些是这个SSD中可用的分区。而这是逻辑块地址(LBA)0到LBA Z-1。LBA在某种意义上是连续的,即在分区0之后,下一个LBA将是前一个LBA加1,而不是重置。所以,如果你看这张图片,我们可以更好地理解分区存储模型。
如果我们把这个整体看作是一个具有n个分区的SSD,那么我们可以了解分区的某些参数或属性。首先,我们需要理解所有分区都是顺序写入要求(sequentially write required)类型,这意味着所有分区只能顺序写入。不允许随机写入。不过允许随机读取。每个分区都有某些属性,比如一个分区大小。分区大小表示每个分区的LBA数量。它在整个设备中是相同的。并且分区大小必须始终是2的幂值。还有分区容量。分区大小必须是2的幂值,但分区容量可以是大于零且小于或等于分区大小的任何值。它是每个分区可写入的LBA数量。分区容量有助于固件管理其NAND,使其不必遵守主机通过TPR(应为规范)强加的2的幂约束。并且由于某些设备NAND特性,很难在不损失效率的情况下将NAND精确保持为2的幂值。还有分区状态。每个分区都经历一个状态机,指示其当前状态。所以每个分区都有一个状态。我们将在下一张幻灯片中介绍分区状态。
但如果我们看这里的这张图片,所以分区0(zone zero),如果我们看分区0,它的起始LBA(starting LBA)是零,写指针(write pointer)就在这里。它显示该分区已被写入到这个点,分区容量是60 MB。这只是举例。因此,从60 MB到64 MB,基本上是一个空洞(hole)。你不能在那个区域进行读取或写入。同样地,这里分区1从64 MB开始,在60 MB之后(即64 + 60 = 124 MB),从124 MB到128 MB。再次,这是一个不可读或不可写的区域。所以,这就是分区容量和分区大小。在分区容量范围内,我们可以写入,可以读取,但在分区容量之后直到分区大小,我们不能使用那部分。那里没有NAND。另一点是写指针(write pointer)。当我们向一个分区写入时,写指针会不断递增。如果我们写入一个LBA,当写指针最初在零时,如果我们写入一个LBA,写指针就增加一,依此类推。
所以,这是分区状态机(zone state machine),每个分区都有一个关联的状态机。该状态机有一组状态,每个状态定义了该分区的操作特性。通常,分区从**空(Empty)状态开始。从空状态,如果我们发出一个打开(open)命令,它会进入显式打开(Explicitly Open)状态。如果我们直接开始向一个空分区写入,它会进入隐式打开(Implicitly Open)状态,然后进入满(Full)**状态。显然,当我们完全写入分区时,它会进入满状态。然而,即使分区在物理上并未完全填满,我们也可以发出命令使其转换到满状态。**离线(Offline)**状态表示分区有问题,或者基本上无法处理I/O。这些是不同的分区状态。
那么,分区命名空间命令集。新的TP4053规范源自基础NVMe规范,它保留了基础NVMe命令,如读、写、刷新、关闭等命令都被保留。除了这些之外,还添加了新命令。例如,区域管理发送命令。这些命令用于管理分区。
重置分区命令基本上擦除分区,意思是它擦除该分区中的所有数据。假设一个64 MB的分区,写指针在20 MB。当你重置这个分区时,写指针移回零,它准备好再次从分区起始处开始写入。分区状态转换到空。
打开分区命令基本上将分区转换到打开状态。
关闭分区命令将分区转换到关闭状态。
完成分区命令将分区状态转换到满。
当我说“关于写入位置”时,我想说的是,当一个分区显然被完全写满时,固件本身或SSD本身会将分区状态移动到满,然而,用户或应用程序也可以将分区状态移动到满,无论当前的写入位置如何。即使它没有满,用户也可以发送命令使分区转换到满状态。
区域管理接收命令(zone management receive commands)。这是一个用于报告、获取分区**区域描述符(zone descriptor)**报告的命令。区域描述符也是分区的一个属性,它基本上定义了主机希望维护的分区的某些可自定义特征。
区域追加命令(zone append command)。这是另一个命令。这基本上是一个写命令。使用区域追加,写入被发送到分区的起始LBA。而驱动器返回该分区内发生该写入的LBA。我会讲到为什么需要这个。
是的,区域追加(zone append)。ZNS规范要求分区只能顺序写入。因此不允许随机写入。因此,如果对分区中LBA的写入以乱序到达驱动器,这些写入将会失败。我想说的是,我们发出一个对LBA 0的写入,然后发出一个对LBA 1的写入,接着发出一个对LBA 2的写入。现在由于某种原因(这是可能发生的),对LBA 2的写入请求首先到达驱动器。驱动器首先处理那个请求。由于基础NVMe规范允许设备以任意顺序完成命令,基础NVMe规范说你可以按照接收到的顺序处理命令。所以设备可以处理写入LBA 2的请求。但它试图写入LBA 2。而写指针设置在零。你不能向分区写入任何东西。你只能在写指针所在的位置写入。所以现在你违反了ZNS规范。因此,这两种规则在某种程度上是相互正交(冲突)的。
一个解决方案是在主机端对写入请求进行序列化。并且每个分区只能维持一个未完成的写入请求,从而消除对分区进行乱序写入的可能性。所以我们的做法是,从主机本身,我们只向一个分区发送一个写入请求,并且只有一个未完成请求。也就是说,如果你发送一个对LBA 0的请求,那么你等待它完成。一旦完成,然后你发送一个对LBA 1的写入请求。因此,在某种程度上,你在对其进行序列化。现在设备不会以乱序接收到它,因为它只按顺序接收,并且只有一个请求。这样你就不会遇到这个写入错误。但是主机端的这种序列化降低了设备的并行性。也就是说,你的I/O深度只有1,这不太好,并且增加了主机CPU开销。而且,为了获得更高的吞吐量而打开更多分区对应用程序来说可能没有意义。同时,它也与驱动器的更多属性正交(冲突)。所以一个应用程序可能只想同时写入特定数量的分区。我们不能强迫应用程序写入它不想写入的额外分区,仅仅因为我们为了提升吞吐量(因为我们只维持队列深度为1)。这是一个问题。
另一个问题是,NVMe规范有称为MAR和MOR的属性,它们定义了最大活动资源(maximum active resources)和最大开放资源(maximum open resources)。最大活动资源和最大开放资源基本上告诉我们在驱动器中任何给定时间点可以打开的最大分区数。因为维护开放分区在驱动器中有开销。它需要一定量的资源。所以在任何给定时间点可以打开的(分区)数量是有限的。因此,为了提高吞吐量,我们不能在任何给定时间点拥有无限数量的分区或所有分区都打开。所以这也阻碍了我们在ZNS驱动器上可以实现的最高吞吐量。
所有这些问题都是在尝试进行正常写入时发生的。我们可以通过排队多个写入请求到你的分区来克服这些挑战。并且设备以它接收到的任何顺序追加写入。它返回数据实际写入的LBA。这是与我们今天做法的一个巨大转变。
所以我们在这里提议的是:现在你发送一堆对LBA 0, 1, 2, 3, 4, 5的写入请求。所有这些请求都发送到… 再次说明,这些请求不是发送到LBA 0, 1, 2, 3, 4, 5。现在当我们使用区域追加发送写入请求时,我们将其发送到分区的起始LBA。所以每个请求都会说这是发送到LBA 0。我的意思是,当你想写入分区0时,你会说这是发送到LBA 0,我想写入这么多LBA。这是数据缓冲区。这是我想写入的数据。所以你可以发送任意多的请求,直到容纳到分区大小或分区容量为止。所有这些请求可以以任何顺序到达驱动器。
现在,一旦它们到达驱动器,驱动器将获取该请求并简单地**追加(append)**到写指针所在的位置。无论写指针在哪里,它获取一个请求,在写指针处写入它。因此写指针递增。然后对于该写入请求,设备返回写指针当时所在的值。所以它基本上是说:好的,我收到了你的请求。我写入了你的数据,这里是我放置你数据在这个分区中的位置。现在应用程序需要做一项额外的工作来获取这个返回值。而这正是它需要读取我们刚刚写入的数据的地方。所以,是的,这对应用程序来说是一项额外的工作。这是需要的定制化,但它解决了序列化问题。
好的。我们刚刚看到了ZNS的特性、特殊之处或不同方面。现在,为了了解传统SSD,在传统SSD中,与ZNS一样,它们也有NAND芯片,SSD控制器管理这些NAND芯片并通过存储接口暴露存储。但在传统SSD中,所有NAND都由**闪存转换层(FTL)**管理。有一个闪存转换层,SSD管理着NAND。然而,在ZNS中,数据管理或数据布局是由主机管理的。
在NAND芯片中,NAND芯片由阵列块组成,这些阵列块内只能顺序写入。并且这个阵列块在写入新数据前必须被擦除。每个阵列块的擦除次数有限,每个阵列块都有一定的寿命(编程/擦除周期)。一旦达到其限制,它基本上就… 阵列块就归还给SSD(意指报废)。因此SSD的寿命就减少了。
所以,如果你在宏观层面看这个,这与ZNS施加的限制更相似。因此,这就像是ZNS将NAND特性提升到了一个更高的抽象层次。所以,这就是传统SSD的工作原理。
现在我们来看看ZNS的优势。
降低了写入放大。它是如何降低写入放大的?通常,写入放大类似于有多少额外的写入是在设备上完成的。基本上,如果应用程序向驱动器写了1GB,而在驱动器内部,它写了4GB,那么写入放大因子(WAF)就是4。当我说降低写入放大时,我指的是设备写入放大,而不是应用程序写入放大。我们有许多其他应用程序,比如RocksDB,它有自己的应用级写入放大。也就是说,如果我在键值存储中创建一个1GB的数据库,它实际向设备写了多少。这就是应用级写入放大因子。根据工作负载的不同,它有时也可能更高,通常在RocksDB中4到5是常见的。这是应用级写入放大。
设备级写入放大发生在设备已满并且设备中存在大量碎片化时。然后设备(我指的是传统SSD)需要回收其一些已被无效的空间。所以,我会在下一张幻灯片中展示一个很好的图形来理解传统SSD中的碎片化。这在ZNS中被消除了,因为在ZNS中,应用程序可以进行智能数据布局,通过进行智能数据布局,它可以完全避免设备级的垃圾回收。通过消除设备级垃圾回收,它降低了… 设备中的垃圾回收是设备中写入放大的根本原因。所以如果我们减少或消除那个垃圾回收,那么设备级写入放大几乎为零或为一,这是最佳情况。
通过降低写入放大,我们提高了吞吐量,因为设备不忙于进行垃圾回收,并且所有设备带宽都可用于主机。
通过减少设备级的垃圾回收,我们减少了设备级的编程/擦除周期,从而延长了SSD的寿命。
它减少了SSD中的内存占用。传统SSD维护着庞大的FTL。随着当前SSD容量增加到TB级(8TB,16TB),闪存转换层变得非常庞大。为了容纳这个以及其他存储控制器的要求,设备上所需的DRAM量也很高。这增加了SSD的成本。但在ZNS驱动器中,我们不需要在驱动器中有FTL,因为主机在管理映射。这降低了成本,你不需要那么多DRAM,从而降低了成本。
在传统SSD中,SSD的**过度配置(over-provisioning)**通常为7%到28%。我们想说的是,我们为获得的NAND多支付了7%到28%,因为那么多NAND在那里,但对用户不可用。这些NAND被用来容纳垃圾回收。在ZNS中,那部分NAND也对用户可用。所以你花同样的钱获得了更多的NAND。这是ZNS的另一个优势。
可预测的延迟。通过可预测的延迟,我的意思是当设备垃圾回收(GC)启动时,会发生很多事情:数据正从一个地方复制到另一个地方,一些擦除块正在被擦除,写入正在发生,擦除正在发生。当这些事情发生时,其他读取请求或写入请求会被延迟。因此它们的延迟增加。通常,擦除时间或擦除延迟以及写入延迟相对于SSD上的读取延迟来说是巨大的,并且这些GC活动增加了读取请求的尾部延迟(,这给主机层面带来了非常糟糕的性能表现。但在ZNS中,我们基本上消除了设备级的垃圾回收,因此这些活动不会在驱动器上发生,延迟在ZNS上变得可预测。
是的,这就是我之前谈到的设备垃圾回收。所以这些传统SSD有闪存转换层,它将逻辑块映射到介质的物理地址,并且写入被写入到由SSD决定的物理地址。因此SSD进行数据布局,导致具有不同预期寿命的文件被存储在同一个擦除单元中。删除或覆写文件会导致驱动器碎片化。
如果你看数据,有时你可以按温度对数据进行分类,比如热数据是更新更频繁、更改更频繁的数据;冷数据是存在时间长、长时间不变的数据。如果我们能智能地放置这类数据,就更容易从应用级进行垃圾回收。
如果你看这张图片,我们有文件A、B、C、D写在这里,它们被随意地放置在驱动器中。如果我现在删除文件C,我们可以看到所有粉红色的无效区域。现在,我们不能回收显示空间(应为回收空间)。例如,如果你看擦除单元,如果B在那边… 我们无法回收这些空间,除非对它们进行擦除循环。而要进行擦除循环,我们需要将那些擦除周期内的有效数据块复制到一个新的擦除单元中。所以我们复制这些紫色的部分(文件B和文件D的紫色块)到一个新区域。一旦复制完成,我们就可以擦除擦除单元A和B。垃圾回收的副作用只有在SSD达到其容量时才变得明显。当这些活动发生时,就像我之前解释的,延迟会飙升。所以,是的,这就是为什么解释这个。
我认为垃圾回收的成本是写入放大因子(WAF)以及主机写入数据后数据在设备内被重写的次数。主机写入的数据示例:如果主机写入10MB数据,而垃圾回收为容纳这10MB在内部完成的写入是15MB,那么我们看到的写入放大因子是1.5。增加的写入放大因子降低了写入吞吐量,因为驱动器内部发生了大量活动,大量写入发生在驱动器内部,写入带宽对主机不可用。并且由于这些活动,更高的读取延迟,就像我预期的那样,读取延迟会飙升。设备成本增加,因为我们进行了大量过度配置(over-provisioning),这部分NAND也需要付费。设备寿命减少,因为垃圾回收在这些单元上进行了大量擦除循环,而在ZNS中这些擦除循环可能不会被进行。因此这降低了传统SSD中的设备寿命,而ZNS设备中则不是这种情况。
当我们谈到ZNS设备时,我们在数据布局上进行协作以降低写入放大因子。因此,我们使主机和存储设备能够在数据布局上协作,使得数据被顺序写入,并且具有相似预期寿命的数据可以一起放置在SSD中。因此,谨慎的数据布局可以消除设备端垃圾回收,从而带来更高的写入吞吐量、更低的读取延迟、降低的设备成本和增加的设备寿命。
如果你看这张图片,在传统的NVMe SSD上,数据是混合的,不同应用程序的数据是混合的。而在ZNS SSD中,我们可以看到相似类型的数据被放置在一起。所以如果应用程序2想删除它的数据,很容易在一个擦除单元内全部无效化。很容易在一个擦除单元内使所有数据无效。然后,你知道,这里不需要复制,不需要从应用程序2复制某些文件的有效块。这就是数据布局可以做到的。
如果你看这个(图表),它显示了设备垃圾回收的影响,视觉效果好得多。所以如果你看蓝线,它是ZNS驱动器的写入吞吐量。绿线是具有28%过度配置的传统SSD的写入吞吐量。橙线是具有7%过度配置的(传统SSD)。这里,如果你看这个,直到这个点(时间点),所有三个的写入吞吐量都几乎相同。然而,在这个点,设备垃圾回收启动,设备垃圾回收启动的那一刻,性能就下降了。在28%过度配置下,它降到了这么低,几乎下降了50%。而在7%过度配置下,设备压力非常大,以至于吞吐量几乎只有它本可提供的20%或30%。为了展示这个,我们有一个使用中的预生产ZNS驱动器和生产中的传统SSD。这说明了ZNS的优势。
软件栈,已经支持。我们在5.9+ Linux内核中有ZNS支持。文件系统方面,我们有对F2FS、zoneFS的支持。在SPDK中,我们有支持。用户空间库和应用程序方面,FIO、NVMe-CLI、blkzone、libzbd、libnvme现在都已可用。在RocksDB、MyRocks(MySQL)、Ceph方面,相关工作都在进行中,我们预计在未来几个月内将它们上游化。
所以这是生态系统。这些是SMR驱动器,这是分区命名空间ZNS驱动器。我们有对RocksDB、FIO的支持,然后是Ceph,以及F2FS、zoneFS,但(支持)正在到来。这是软件生态系统。
Linux null block,在块设备模拟这里。Linux null block驱动程序支持ZBD模拟(ZBD emulation),提供了一种简单的方式来获得分区设备的实践经验。所以你可能在想,我没有分区设备。我怎么获得更多细节?我怎么深入?这是一个模拟。它使用系统RAM,创建一个具有所有这些ZNS特定属性的设备,我们可以用这个设备进行实验。因此,内核配置必须启用这个内核配置CONFIG_BLK_DEV_ZONED
,然后我们使用这些命令加载null block驱动程序,就可以创建一个内核块设备。一旦这个设备准备好,我们就可以用它进行实验。但再次说明,这不是NAND,它使用的是DRAM。如果你有实际的SSD,那很好。你可以使用FIO、libzbd和NVMe命令来查看发生了什么,情况如何,甚至更深入地理解。
所以,这是使用FIO进行随机写入。当我们说随机写入FIO时,我们在FIO中添加了支持。但当我们说随机写入时,分区是随机选择的,写入在那些随机选择的分区内是顺序发生的。因此这个工作负载的名称可能有点令人困惑,但它仍然是在分区内顺序写入,只是分区选择在ZNS SSD内是随机的。
这是zbd,它带有libzbd,它基本上报告有多少个分区,分区大小是多少,分区容量是多少,分区状态是什么,属性是什么,或者分区索引之类的信息。
现在我们想谈谈F2FS对ZNS的支持。F2FS是一个日志结构文件系统,根据定义,它以一种顺序的、类似日志的结构将所有数据及其修改写入磁盘。F2FS对分区块设备的支持是为SMR设备添加的。我认为在当前的Linux 4.9中(应为更早版本),它很久以前就可用。我添加的是支持处理区域容量。SMR设备没有区域容量的概念,它们有的是分区大小,但在ZNS中,有区域容量这个概念。为了适应区域容量,我们需要在F2FS文件系统中添加一些支持。
F2FS对其元数据执行就地更新(in place updates)。在LFS模式下,数据被顺序写入主区域(main area)的段(segments)。在非LFS模式下,F2FS有时也会对数据进行就地更新。我们需要让它处于LFS模式,以便在ZNS设备上托管F2FS。并且需要一个额外的常规块设备来托管F2FS的元数据以及ZNS设备。
F2FS对元数据进行就地更新,但就地更新在ZNS驱动器上是不可能的。因此想法是拥有一个常规块设备,与ZNS设备一起,并将它们合并创建一个F2FS卷,然后使用该F2FS卷来创建F2FS文件系统。
如果你看这个布局(,F2FS文件系统有超级块、检查点段(checkpoint segment)、信息表(sit table)、节点地址表(nat table)、段摘要区(segment summary area)和主区域(main area)。主区域是数据存放的地方,其他区域是元数据区域。
当我们进行格式化时,基本上,主区域放在ZNS设备上,所有其他区域放在常规块设备上。主区域由**区段(sections)组成,每个区段由段(segments)**组成。这就是布局方式。
现在,一个ZNS分区被映射到一个F2FS区段。所以区段大小等于分区大小。这已经存在。但问题是现在分区大小不等于分区容量。所以,在那些分区容量小于分区大小的情况下,落在该范围(即分区容量之后直到分区大小)内的段在挂载时被文件系统标记为满。我们所做的是查看分区容量,然后查看落在分区容量之后直到分区大小的段。我们完成… 如果… 我们将所有这些段标记为满,这意味着这些段在F2FS文件系统中不会被分配用于写入。这避免了写入分区容量之后的区域。
如果你看这张图片,红色块是分区容量结束处的块。这些段被标记为满,基本上意味着它们不可用于任何写入,并且在垃圾回收期间,这些段也会被忽略。
如果分区容量是奇数大小,那么相应的段只使用到分区容量的LBA。所以如果分区容量是60MB,那么在一个64MB的分区中(假设F2FS段大小为2MB),我们有32个段(64/2=32)。如果分区容量是60MB,那么我们实际上只有30个段(在32个段中)可以写入。段30和段31不可写。所以我们标记它们为满,它们不会被分配用于写入。
但是,如果分区容量是61MB呢?如果分区容量是61MB,那么段30(或段29,取决于起始)将只有1MB的可写空间(61MB - 60MB = 1MB,但需根据段边界计算)。我们也管理这些块。因此,由于对齐问题,不会有任何NAND被浪费。
那么,在ZNS设备上创建F2FS文件系统的步骤在这里。我们需要一个块设备。如果没有块设备可用,你可以为元数据创建一个普通的null块设备。不需要是分区块设备,普通的块设备即可。或者如果你有另一个常规SSD或任何其他块设备,那也可以。并且确保ZNS驱动器使用deadline调度器。就像我之前说的,区域追加在F2FS中还不支持。因此写入必须被序列化,并且序列化是在deadline调度器中实现的。所以我们必须为这些设备设置deadline调度器。
这是格式化命令,-o overprovision=5%
和 -m zoned
。这是挂载命令。一旦你运行这个,你就得到了挂载路径上的F2FS文件系统。现在你写入的任何数据都会进入ZNS设备,如果主区域从那个常规块设备开始,并且该常规块设备上有空间可用,数据也可能进入那个区域。
现在我们谈谈使用ZNS的挑战。我们现在可能已经了解到,我们需要修改应用程序。没有应用程序可以直接运行。这不是即插即用的SSD。你拔出一个传统SSD,插入一个ZNS SSD,那行不通。所以任何优化或好处都是有代价的。代价是我们需要修改应用程序。我们需要修改操作系统以处理分区语义。应用程序现在还必须根据数据的预期寿命来管理其数据布局。通过这样做,它们开始在性能和成本方面获益。
另一个问题是,很多文件系统尚未获得支持,操作系统也是。
更多信息,你可以在这里获取规范:nvmexpress.org,它是TP4053。你可以获取更多分区存储信息:zonedstorage.io / introduction / zns。更多关于F2FS的信息在kernel.org。这是我们在GitHub上RocksDB对ZNS驱动器支持的链接。是的,请务必阅读会议资料。谢谢。祝你有美好的一天。
是的,请务必阅读会议资料。