告别无意义的IOPS

“你需要多少IOPS?”

存储供应商经常问我这个问题,而这个问题让我有点烦恼。我认为他们问这个问题是因为其他客户提出了明确的IOPS要求,但我认为如果被要求,任何人都会很难解释一个 I/O 操作(相对于一个GB)的科学价值。尽管如此,不可否认的是,IOPS非常重要;备受推崇的Rob Ross在最近的ASCAC会议上专门给了这个问题一个幻灯片。

Rob Ross 对 IOPS 为何现在对 HPC I/O 很重要的看法

我同意Rob所有的观点,但我对他幻灯片的标题持不同意见;IOPS是愚蠢的,但在当今时代,在设计高性能并行文件系统时忽略它们是更愚蠢的。所以让我们来谈谈这两者之间的灰色地带,它造成了这种二分法。

首先,”带宽“是相当愚蠢的

如果在HPC领域有一个不变的事实,那就是每个人都讨厌I/O。而且有一个很好的理由:因为每秒钟等待I/O完成都是你没有进行数学计算的时间,而最初就是因为数学计算才让你使用超级计算机。I/O是在一个名为“高性能计算”的领域中你在进行零计算的时间。

话虽如此,每个人都欣赏I/O的产物——数据。I/O是保存计算结果的必要部分,因此没有人会说他们希望没有I/O。相反,人们希望的是无限快速的I/O,因为这意味着科学家在使用HPC时100%的时间都用于执行计算,同时在作业完成后仍保留了计算结果。

再深入了解一层,计算的保存结果——数据——具有内在的价值。在典型的模拟或数据分析中,每个字节的输入或输出通常是人或机器进行大量工作的辛勤成果,因此如果你想保存大量字节但又希望尽可能少地花费时间进行I/O,那么并行存储系统性能的真正价值在于它每秒可以读取或写入多少字节。从根本上讲,这就是为什么长期以来I/O性能的衡量单位是MB/s、GB/s,现在是TB/s。对外行来说,一个能提供100GB/s的文件系统比一个只能提供50GB/s的文件系统更有价值,假设其他条件相同。很简单。

然而,一旦你开始尝试设定关于这个指标的期望,它就很快会变得复杂。例如,假设你的HPC作业生成了21TB的有价值数据,这些数据必须频繁存储,我们真的不能容忍在写入这些数据之前花费超过30秒的时间,否则我们会感觉“花费太多时间”在I/O上而不是在计算上。这相当于700GB/s,这是一个相当任意的选择,因为30秒是主观的一个时间,但它反映了你21TB的价值和你的时间的价值。理应如此,任何声称具有700GB/s写入能力的文件系统都应该满足你的要求,并且任何能提供这样系统的供应商都应该得到你的业务,对吗?

当然不是。众所周知,要获得这些高性能带宽,就像要获得Linpack级别的FLOPS一样,需要你(终端用户)以完全正确的方式进行I/O操作。对于上述提到的700GB/s文件系统,这意味着:

  • 每个MPI进程都写入自己的文件(单个共享文件会因文件系统锁定流量而减慢速度)
  • 每次写入4 MiB(以完全匹配网络传输缓冲区、远程内存缓冲区、RAID对齐等的大小)
  • 每个节点使用4个进程(足够的并行性以驱动网卡,但不会过多阻塞节点)
  • 使用960个节点(足够的并行性以驱动所有文件系统驱动器,但不会过多阻塞服务器)

我从未见过科学应用程序以这种准确的模式执行,因此,实际上我不认为有任何科学应用程序能够从“700GB/s文件系统”中获得700GB/s的性能。从这个意义上说,这个700GB/s的带宽度量指标相当愚蠢,因为没有人真正实现了它的额定性能。当然,这并没有阻止我在宣传文件系统时说出这些愚蠢的话。然而,将带宽作为I/O性能的一个有意义的度量标准的唯一救赎之处在于,I/O模式是一种人为构建的结构,可以压缩、拉伸和重塑,而不会影响底层传输的科学数据。

数据的价值在于其内容,而不是它的排列或访问方式。从科学的角度来看,人们无需按照4 MiB一次读取数据,只要这些位最终能够按正确顺序传递给将对其进行计算的CPU即可。HPC用户进行良好的、1 MiB对齐的读写的唯一原因是因为他们(无论是在培训中还是在实践中)了解到随机读取几千个字节一次非常缓慢,并且与他们最小化I/O时间的利益相悖。这与HPC的计算方面形成了鲜明对比,HPC的计算方面通常遵循物理定律,这些定律决定了必须进行计算的方程,而这些计算发生的顺序决定了最终结果是否准确地模拟了某个物理过程,或者仅仅产生了一些不合理的垃圾结果。

因为I/O模式本质上没有价值,所以我们可以自由地重新排列它们,以最大限度地适应存储系统的优点和缺点,以实现最大的GB/s输出。这就是MPI-IO的整个基础,它接收对于正在模拟的物理过程而言方便的I/O模式,并将其重新排序为对于存储系统而言方便的模式。因此,虽然说一个文件系统可以提供700GB/s有点不真实,但它确实表明了如果你愿意扭曲你的I/O模式以完全匹配设计最佳状态,那么可能会实现的性能。

但是”IOPS“特别愚蠢

IOPS是从带宽这样的基于价值的性能度量中剥离出来的结果。与其表达一个文件系统每秒可以移动多少有价值的字节,IOPS表达的是一个文件系统每秒可以处理多少任意的I/O操作。而由于“I/O操作”的概念完全是人为构建的,并且可以进行扭曲而不会损害底层数据的价值,你可能已经明白为什么IOPS是性能的愚蠢度量标准了。它们测量的是文件系统可以多快地执行一些毫无意义的操作,而这个无意义的操作(I/O操作)本身就是文件系统的一个功能。这就像说你可以以每秒五步的速度跑一场马拉松一样——它实际上并不表示你需要多长时间来跑完这26英里。

IOPS作为性能度量在大部分历史上对于HPC而言是相对较为陌生的。直到2012年,HPC存储主要由硬盘主导,硬盘仅在大规模连续读写操作中提供高价值的性能,而”IOP”的概念与性能背道而驰。闪存的出现引入了性能的新维度,因为它能够在文件内或整个文件系统中以不连续(甚至随机)的位置读取和写入大量数据。毫无疑问:使用连续的I/O模式,你仍然可以读取和写入更多的字节/秒(即获得更多的价值)。闪存只是提高了性能的底线,以防止你无法或不愿以方便存储介质的方式扭曲应用程序执行I/O操作。

因此,当供应商宣传他们能够提供多少IOPS时,他们实际上是在宣传在最坏情况的I/O模式(完全随机偏移)下能够提供多少不连续的4 KiB读取或写入。你可以通过将供应商的IOPS性能乘以4 KiB来将其转换回一个有意义的值指标;例如,我曾经展示过一张幻灯片,声称我从单个ClusterStor E1000 OST阵列中测量到29,000个写IOPS和1,400个读IOPS:

单个ClusterStor E1000 NVMe Lustre OST的性能测量

实际上,我能够以0.12 GB/s的速度写入数据,以5.7 GB/s的速度读取数据,并且以IOPS的方式陈述这些性能指标清楚地表明这些数据速率反映了最坏情况下在随机位置发生的微小I/O,而不是最佳情况下的顺序I/O,后者可以分别以27 GB/s和41 GB/s的速度进行。

当我们试图将IOPS视为某种类似于上述讨论的700GB/s带宽度量的炫耀的数字时,IOPS变得特别愚蠢。因为IOPS反映的是最坏情况下的性能场景,所以没有用户应该问“如何获得最高的IOPS”,因为他们实际上是在问“如何获得最好的最坏情况性能?”相关地,试图测量存储系统的IOPS能力变得非常复杂,因为它通常需要以非常不现实的方式扭曲你的I/O模式,以获得如此糟糕的性能,这需要付出巨大的努力。在某个时候,每个I/O性能工程师都应该质疑为什么他们要花费这么多时间来破坏文件系统实现的每个优化,以避免这种最坏情况的发生。

为了使这个问题更具体化,让我们看看我在2019年制作的一张幻灯片,讨论了这个完全相同的ClusterStor E1000阵列的IOPS预测:

基于PCIe Gen3平台的ClusterStor E1000 NVMe Lustre OST的预期性能

令人惊讶的是,随机读取速率从预计的600,000个读IOPS增加到了令人惊人的1,400,000个读IOPS,哪一个是正确的读IOPS度量?

事实证明,它们两个都是正确的;测得的读IOPS数量差异巨大是因为600,000个IOPS的估计来自于一项持续时间更长的测量(180秒对比69秒),使用的客户节点较少(21个节点对比32个节点),以及写入了更大的文件(1,008个8 GiB文件对比1,024个384 GiB文件)。

与使用标准工具(单个节点上的fio和libaio)测量单个SSD的IOPS不同,没有标准方法来测量并行文件系统的IOPS。而且,就像我们上面讨论的炫耀的带宽数字对于真实应用程序是无法实现的一样,任何针对并行文件系统的标准化IOPS测试都将得出一个相对无意义的数字。是的,这也包括IO-500;如果你想以正确的方式设计并行文件系统,那么它的数值在定量上没有多少价值。

那么,谁能说一个ClusterStor E1000 OST是否能够达到600 kIOPS或1,400 kIOPS呢?我认为1,400 kIOPS更准确,因为I/O是突发性的,而完全随机读取持续三分钟的突发时间比在生产系统上持续一分钟的突发时间更不太可能。如果我为供应商工作,我相信这将被视为不诚实的营销数字,因为它并不反映出无限持续的性能水平。或许勇敢地说,正式的Cray ClusterStor E1000数据手册甚至不会涉及这些问题,也不会引用任何IOPS性能预期。最终,随机读取能力的真正价值在于在文件系统上同时运行的所有最随机的工作负载能够实际实现的带宽。祝好运去弄清楚这一点。

而写”IOPS“真的很愚蠢

正如我在开始时所说,我对Rob在ASCAC上呈现的幻灯片中的所有要点都没有异议。其中第一个要点尤为重要——在AI等领域,出现了一类新的HPC工作负载,其主要目的是随机抽样大型数据集以训练统计模型。如果这些数据集太大而无法放入内存中,你就无法避免一定程度的随机读取I/O,否则将引入权重偏差。因此,HPC确实需要从其文件系统中要求高随机读取性能。将此要求转化为4 KiB随机读取速率以对“你需要多少IOPS”这个问题给出一个明确的答案是可疑的,但无论如何,在HPC中几乎没有空间供纯粹的智力思考。

而对于随机写入速率,情况则不同。在并行文件系统中,写IOPS是一个完全无价值且具有误导性的性能度量指标。

在大多数情况下,HPC应用程序都在近似物理世界的某个方面,并且数学和物理学被创造出来以结构化方式描述这个物理世界。无论你是在计算原子、网格还是矩阵,你所写出的数据以及应用程序遍历内存以写入所有数据的方式都具有结构。你可能不会以完全有序的方式写出数据;一个MPI进程上可能会有更多的原子,或者你可能正在遍历一个不平衡的图。但科学数据几乎总是具有足够的结构,可以使用MPI-IO等中间件将其压缩成非随机的I/O模式。

当然,也有一些工作负载不适用于上述情况。对短读取DNA序列进行离线排序和对望远镜马赛克图像进行原位更新是两种工作负载的例子,其中你不知道要将小量数据写入何处,直到对该小量数据进行计算。但是,在这两种情况下,文件永远不会同时读取和写入,这意味着这些随机式写入可以在内存中进行缓存、重新排序以减少随机性,并以异步方式写入文件。而写回缓存对随机写入工作负载的影响是惊人的。

为了说明这一点,让我们来看看在对全闪存文件系统运行IOR以测量随机4 KiB写入时可以采用的三种不同方式:

  • 在naïve情况下,我们只是在一堆文件中的随机位置写入4 KiB页面,并在结束时报告IOR告诉我们写IOPS。这只包括在write(2)调用中花费的时间。
  • 在包括fsync的情况下,我们在所有写入操作结束时调用fsync(2),并将其返回所需的时间与write(2)中的所有时间一起包括在内。
  • 在O_DIRECT情况下,我们使用直接I/O打开文件,完全绕过客户端写回缓存,并确保write(2)在数据写入文件系统服务器之前不返回。

这些看似微小的变化导致的写IOPS速率相差超过30倍:

在全闪存并行文件系统上,使用IOR进行测量的随机写IOPS

再次问一下:哪个值才是文件系统的正确写IOPS性能?

如果我们分解这个I/O性能测试的每个阶段所花费的时间,我们会立即发现naïve情况是极具误导性的:

工作负载中的I/O调用时间分解

IOR报告260万的写IOPS速率是因为所有这些随机写入实际上都被缓存在每个计算节点的内存中,直到文件关闭和所有缓存的脏页被刷新时才进行实际的I/O。在这种情况下,缓存刷新过程不再产生随机写入;客户端将所有这些缓存写入重新排序为大型的1 MiB网络请求,并将我们的随机写入工作负载转换为顺序写入工作负载。

在包括fsync的情况下,发生的情况是完全相同的;唯一的区别在于我们将缓存刷新所需的时间包括在IOPS测量的分母中。令人沮丧的是,我们实际上在45秒后停止了write(2)调用,但是在那45秒内,内存中缓存了如此多的写入数据,以至于在最后的fsync和文件关闭期间,需要将它们全部重新排序并写入文件系统花费了将近15分钟的时间。本应在45秒内向文件系统进行随机写入的情况变成了在45秒内向内存进行随机写入,以及在850秒内向文件系统进行顺序写入。

O_DIRECT情况是最直接的,因为我们不缓存任何写入操作,应用程序的每个随机写入操作都会成为对文件系统的随机写入操作。这使我们测得的IOPS几乎减少了一半,但在预期仅进行45秒的写入时并不会有任何意外。当然,在这种情况下,我们总体上写入的字节数要少得多,因为在这45秒内的有效字节/秒非常低。

基于这一切,我们很容易说O_DIRECT情况是测量随机写IOPS的正确方式,因为它避免了写回缓存——但事实真的如此吗?在应用程序故意进行随机写入的罕见情况下(例如,离线排序或原地更新),不同节点上的两个MPI进程会尝试同时向同一文件的同一部分进行写入的机会有多大,从而触发缓存刷新?更直接地说,科学应用程序同时使用O_DIRECT和随机写入的几率有多大?只有最受折磨的HPC用户才会有意识地做这样的事情,因为这会导致最坏情况下的I/O性能;用户很快就会意识到这种I/O模式很糟糕,并改变他们的I/O模式以提高超级计算机的有效使用。

那么,如果没有理智的用户真正进行未缓存的随机写入,那么首先测量它的意义是什么呢?没有意义。测量写IOPS是愚蠢的。使用O_DIRECT来测量随机写入性能是愚蠢的,而通过写回缓存来测量写IOPS,虽然代表了大多数用户的实际工作负载,但实际上并没有进行4K随机I/O,因此甚至无法测量IOPS。

并非所有的IOPS都是愚蠢的

尽管如此,在并行文件系统之外的情境下,测量IOPS可以是有价值的。我能想到两种情况,在这些情况下,测量IOPS可以作为一个合理的衡量标准。

1. 为容器和虚拟机提供LUN

根据定义,基础架构提供商不应对运行在黑盒容器和虚拟机中的应用负责,因为他们提供的是存储基础架构(块设备),而不是存储服务(文件系统)。块的输入和输出是以IOPS来衡量的,所以这是一个自然的匹配。但是,HPC用户关心的是文件系统(也就是说,科学应用程序不直接使用SCSI命令执行I/O!),所以关注LUN的性能在HPC环境中没有意义。

2. 测量多个用户执行多个任务的影响

虽然单个HPC工作负载很少有意进行随机I/O操作,但是如果有足够多的用户同时执行许多小任务,文件系统本身会面临接近随机的工作负载。并行运行的越多、小而独立的任务越多,并且你离整体I/O负载时间线越远,它看起来就越随机。因此,我认为对于并行文件系统来说,测量IOPS是公正的,目的是衡量文件系统在开始影响所有人之前可以承受多大的滥用。

以我使用IOR在一个小型全闪存文件系统上进行的IOPS扩展测试为例:

测试以展示全闪存文件系统的饱和点

看起来大约需要4096个并发的随机读写器才能使文件系统达到最大。单独考虑这一点并没有什么意义,直到你考虑到这在整个计算和存储平台的背景下意味着什么。

计算集群中的计算节点中有多少部分对应于4096个核心?比如说,如果你有728个双插槽64核的AMD Epyc处理器,只需要32个计算节点就可以使这个文件系统达到极限。如果另一个用户想要使用剩下的696个计算节点之一运行一个需要读取文件系统中散落的随机包的Python脚本,此时将没有剩余的IOPS容量,每个人都会感到明显的延迟。

当然,这是最极端的情况-纯随机IOPS-但是你可以测量实际工作负载在服务器端产生的IOPS,比如在对深度学习训练数据集进行采样时。通过这个,你可以计算出该应用程序为其他每个类似随机工作负载在同一系统上运行时留下多少余地。

一旦你意识到科学计算中许多不起眼的部分-在登录时读取dotfiles,启动动态链接可执行文件时加载共享对象,甚至只是编辑源代码-都充满了类似随机的读取,你就可以建立一个定量的基础,来确定一个IOPS密集型数据分析应用程序会对其他人的交互访问产生多大影响。

这并不是说我们可以轻易地回答“你需要多少IOPS?”的问题。一个工作负载可以驱动多少IOPS并不是它所需的IOPS数量-实际上,它是指在计算速度耗尽数据并需要读取更多数据之前,它能够计算的速度有多快。通常情况下,计算节点越快,它们可以消耗的数据越多。它们仍然希望获得所有你能给予它们的IOPS,以便尽可能多地进行计算(而不是等待I/O),应用程序可以驱动的IOPS数量取决于它和存储之间的完整堆栈,包括CPU、内存和网络。

如果一切都是愚蠢的,那么现在怎么办呢?

放弃试图将I/O性能简化为单一的IOPS数值,因为它与有用性相差两个层次。带宽是一个更好的指标,因为它与实际问题只相差一个层次,但最终,I/O性能的真正衡量标准是应用程序在进行有意义的计算之前必须等待I/O的时间有多长。诚然,大多数存储供应商会茫然不知地看着你,如果你向他们表明你的应用程序50%的时间都在等待I/O,这并不能从存储公司单方面获得更好的文件系统,所以要思考真正的问题可能是什么。

应用程序是否以(随机或其他)方式进行I/O,阻止存储系统以尽可能多的每秒字节数提供数据?如果是这样,向供应商要求一个能够为更广泛的I/O模式提供更多带宽的存储系统,而不仅仅是完全对齐的1 MiB读写操作。

存储系统是否已经以最佳状态运行,但只需几个计算节点就能达到极限?如果是这样,那么相对于计算系统来说,你的存储系统太小了,你应该向供应商要求更多的服务器和硬盘进行扩展。

存储系统是否在100%的CPU利用率下运行,即使它没有提供完整的带宽?处理小型I/O需要比处理大型I/O更多的CPU资源,因为每次读取或写入都需要进行固定的计算,无论其大小如何。向供应商要求一个不会消耗太多CPU资源的更好的文件系统,或者要求更高性能的服务器。

或者,如果有许多用户在进行不同的操作,而文件系统为每个人提供的性能都很差,那么可以向供应商要求具有更好服务质量的文件系统。这将确保一个大型作业不会让其他小型作业无法运行。

存储系统是否很慢,但你没有时间去找出原因?如果是这样,听起来你工作的组织实际上并不重视数据,因为它没有适当配置人员。这不是一个存储问题!

最终,如果解决I/O问题就像回答你需要多少IOPS一样容易,存储就不会成为HPC中的永久痛点。与计算中的所有事物一样,没有捷径,正确的方法是卷起袖子,开始排除问题。在购买存储时,你可以(而且应该!)向存储供应商提出很多要求-灵活交付带宽、高效的文件系统和服务质量控制都是合理的要求。但IOPS不是其中之一。