译|IOCost: Block IO Control for Containers in Datacenters


  1. 摘要
  2. CCS 概念
  3. 关键词
  4. 1. 引言
  5. 2. 背景
    1. 2.1 使用 cgroup 进行资源控制
    2. 2.2 块层和 IO 控制
    3. 2.3 硬件和工作负载异构性
  6. 3. IOCost 设计
    1. 3.1 概述
      1. 3.1.1 问题路径
      2. 3.1.2 规划路径
    2. 3.2 设备成本建模
    3. 3.3 QoS 和动态 vrate 调整
    4. 3.4 使用 ResourceControlBench 调整 QoS 参数
    5. 3.5 处置优先级反转
    6. 3.6 预算捐赠
  7. 4. 评估
    1. 4.1 低开销
    2. 4.2 比例控制和工作量保持
    3. 4.3 机械硬盘建模
    4. 4.4 QoS 和 Vrate 调整
    5. 4.5 内存管理感知
    6. 4.6 堆叠延迟敏感的工作负载
    7. 4.7 远程存储和 VM 环境
    8. 4.8 包获取和容器清理
  8. 5. 经验教训
  9. 6. 相关工作
  10. 7. 结论

摘要

资源隔离是数据中心环境的基本需求。然而,我们在 Meta 大规模数据中心的生产实践中发现,现有的块存储 IO 控制机制在容器化环境中表现不足。IO 控制必须为容器提供按比例分配的资源,同时考虑到存储设备硬件异构性和数据中心部署工作负载的特性。现代 SSD 的速度要求 IO 控制以低开销执行。此外,IO 控制应追求工作量保持,考虑与内存管理子系统的交互,并避免优先级反转导致的隔离失败。

为应对这些挑战,本文提出 IOCost,一种专为容器化环境设计的 IO 控制方案,它为数据中心中异构存储设备和多样化的负载提供了可扩展、工作量保持和低开销的 IO 控制。IOCost 通过离线分析建立设备模型并用此模型来估计每个 IO 请求的设备占用情况。为了最小化运行时开销,IOCost 将 IO 控制分为快速的每 IO 问题路径和较慢的周期性规划路径。一个创新的工作量保持预算捐赠算法允许容器动态共享未使用的预算。 IOCost 已经在 Meta 的数据中心进行了部署,覆盖了数百万台机器,向上游的 Linux 内核贡献了 IOCost,并开源了设备分析工具。IOCost 已经在生产环境中稳定运行了两年,为 Meta 的设备群提供着 IO 控制服务。我们在此文中详细阐述了 IOCost 的设计理念,并分享了将其大规模部署所积累的经验。

译者注
工作量保持:尽量利用可用资源来执行任务,不让资源闲置

CCS 概念

  • 软件工程 → 操作系统;输入/输出;
  • 计算机系统组织 → 云计算。

关键词

数据中心,操作系统,I/O,容器

1. 引言

容器正在迅速成为现代数据中心中虚拟化容量的主要机制之一。它们在操作系统层面虚拟化资源,为应用程序提供轻量级、一致的运行环境,便于跨平台部署和运行。目前市场上有众多容器解决方案,包括亚马逊 AWS、谷歌 Cloud 和微软 Azure 等主要云服务商提供的产品。容器也正在接管私有数据中心,Facebook 的整个服务器群也完全基于容器运作。随着容器使应用整合程度提高,构建有效的控制和隔离机制变得尤为重要。

以往的研究关注点主要集中在计算、内存和网络资源的隔离上,并在 Linux 中有许多改进。不过,Meta 在大规模数据中心的实际运营中发现,现有针对块存储的 IO 控制机制,例如 BFQ,无法满足容器化环境下的需求。

为容器提供健壮的 IO 控制,存在以下几项挑战:首先,IO 控制需要考虑数据中心中的硬件异构性。单个数据中心中可能同时存在多代 SSD、传统的硬盘驱动器、本地/远程存储和新型存储技术。硬件异构性因它们在延迟和吞吐量方面的性能特性大不相同而进一步加剧,不仅在不同类型的设备(如 SSD 和硬盘)之间,而且在同一类型内也是如此。有效控制还需要考虑 SSD 的特殊性,这些特性可能会在短时间内过度发挥其性能,然后急剧下降,从而对堆叠环境产生不利影响。

其次,IO 控制需要适应各种应用程序的限制。例如,一些应用程序对延迟敏感,而其他应用程序主要从增加吞吐量中受益,还有一些应用程序可能执行顺序或随机访问,这些访问可能是突发的或是持续的。不幸的是,在数据中心级别,当设备异构性和应用多样性结合在一起时,找到延迟和吞吐量之间的平衡点尤其具有挑战性。

第三,IO 隔离需要提供数据中心所需的一系列属性。工作量保持是理想的,因为它能实现高利用率,避免资源闲置。此外,一些 IO 控制机制依赖于严格的优先级排序,但这在平等优先级的应用共享同一台机器时无法提供公平性。再者,应用程序开发者常常无法准确评估每个应用和设备层面上的 IO 需求,比如 IOPS 这样的指标。因此,IO 控制机制应当易于应用程序开发者理解和配置。最后,IO 隔离与诸如页面回收和交换等内存管理操作相互作用。IO 控制必须识别这些交互,以防止优先级反转和其他隔离失败的情况发生。

过去的 IO 控制研究主要集中在基于 VM 的虚拟化环境上,提出了多种旨在增强 hypervisor 功能的方案。然而,这些方法并没有充分考虑到容器环境的复杂性,例如单一共享的操作系统、IO 与内存子系统之间的交互,以及高度堆叠的部署方式。在 Linux 内核中,最先进的解决方案要么依赖于 BFQ,要么基于最大带宽使用量,通过 IOPS 或字节数来设定限制。然而,这些方法未能实现充分的工作量保持(work-conserving),缺乏与内存子系统的整合,或者对于快速存储设备增加了过多的性能开销。这意味着,传统 IO 控制机制在容器化环境下,特别是在需要高效利用资源和与内存管理协同工作的场景中,表现不佳。

在这项研究中,我们引入了 IOCost,这是一个全面的 IO 控制解决方案,它综合地解决了异构硬件设备和应用程序带来的挑战,同时满足了数据中心规模下容器对 IO 隔离的需求,同时考虑了与内存管理的交互。IOCost 背后的关键洞察是,IO 控制中的主要难点在于对设备占用情况理解不足。当我们比较现有的 IO 控制与 CPU 调度时,这一点变得明显。CPU 调度依赖于加权公平队列等技术,通过测量 CPU 时间消耗来按比例分配 CPU 占用率。相比之下,像 IOPS 或字节数这样的指标对于衡量占用率来说并不理想,尤其是考虑到块设备种类繁多。现代块设备严重依赖内部缓冲和复杂的延后操作,如垃圾收集,这给那些依赖设备时间共享或主要基于 IOPS 或字节数来确保公平性的技术带来了难题。

IOCost 通过使用特定设备的模型来估算每个 IO 请求的设备占用量工作。例如,4KB 的读取操作在高端 SSD 上的成本与在传统机械硬盘上是不同的。有了占用模型和额外的 QoS 参数——后者用于补偿建模不准确性并决定设备负载程度——IOCost 可以在各个容器之间公平地分配设备占用。系统管理员或容器管理系统沿着容器层次结构设置权重,以确保单个容器或容器组获得一定比例的 IO 服务。IOCost 进一步引入了一种新颖的工作量保持预算捐赠算法,允许容器高效地将其多余的 IO 预算转移给其他容器。

我们已经在 Meta 的整个机群中部署了 IOCost。我们的评估显示,与其它解决方案相比,IOCost 能提供比例、工作量保持且具备内存管理感知的 IO 控制,同时开销极小。具体而言,我们证明了 IOCost 在堆叠式 ZooKeeper 部署中成功隔离了 IO 操作,而先前的解决方案未能提供可行的解决办法。为了表明 IOCost 的广泛应用性,我们还在使用远程存储如 AWS Elastic Block Store 和 Google Persistent Disk 的公共云 VM 上成功验证了它的有效性。

我们已经在 Meta 的设备群中部署了 IOCost。我们的评估表明,IOCost 优于其他解决方案,提供了比例工作保持和内存管理感知的 IO 控制,且开销极小。具体来说,我们展示了 IOCost 在一个堆叠的 ZooKeeper 部署中成功地隔离了 IO 操作,而现有的解决方案则未能提供可行的解决方案。为了证明 IOCost 的广泛适用性,我们在使用远程存储(如 AWS Elastic Block Store 和 Google Cloud Persistent Disk)的公共云 VM 中成功验证了它。

本文的贡献如下:

• IOCost 提出了一种针对现代存储设备设计的容器感知、可扩展、工作量保持且低开销的 IO 控制方案。
• 我们介绍了一种建模技术,用于评估不同应用和设备上的 IO 设备占用情况。为了弥补模型不精确性带来的影响,IOCost 根据实时 cgroup 使用情况和 IO 完成延迟的统计数据,在运行时调整 IO 控制策略。
• 我们提出了一种工作保护算法,它使得容器能够将未完全使用的 IO 预算按比例捐赠给 cgroup 层级中的其他容器。
• 为了减少运行时开销,我们将 IO 控制分解为快速的每 IO 问题路径和较慢的周期性规划路径。
• 我们对 IOCost 进行了详细的评估,并展示了现有的 IO 控制机制在功能集和性能上无法与 IOCost 相匹敌。
• 我们已在 Meta 公司遍布全球的数据中心(包含数百万台机器)全面部署了 IOCost,并向上游的 Linux 内核贡献了 IOCost,同时开源了我们的设备性能分析和基准测试工具。

2. 背景

在本节中,我们首先简要介绍 cgroup,它是用来配置每个容器资源分配的关键机制。接下来,我们介绍了 Linux 块层和现有的 IO 控制解决方案。最后,我们描述了现代数据中心的背景,其中包含多种不同的块存储设备和工作负载。

2.1 使用 cgroup 进行资源控制

容器运行时依赖于控制组(cgroup)来实现资源控制和隔离。如今,cgroup 是容器组织进程并沿其层次结构以受控和可配置方式分配系统资源的主要机制。

cgroup 有两个主要的概念部分。首先,单个 cgroup 形成了一个层次结构,而进程属于一个 cgroup。一个 cgroup 可以包含大量进程或仅包含一个进程。其次,cgroup 控制器会根据配置,沿着这个树状结构分配具体的系统资源,比如 CPU、内存和 IO。

配置 cgroup 控制器的一种常见方法是使用权重(weight),即通过累加所有同级 cgroup 的权重,然后根据每个 cgroup 权重与总和的比例来分配资源。


图 1:Meta 生产环境 cgroup 层次结构

图 1 显示的是 Meta 使用的一个示例性 cgroup 层级结构。这个层级被划分为系统(system)、主机关键(host critical)和工作负载(workload)三个部分的 cgroup。System cgroup 包含了所有的辅助服务,比如 chef,服务通常执行定期操作以保持主机更新。Host Critical cgroup 则包括了维持主机运行所必需的进程,例如 sshd 和容器管理代理。Workload cgroup 则存放了所有应用程序的进程,为了适当地隔离不同的容器,它被进一步细分为子 cgroup。

2.2 块层和 IO 控制


图 2:IO 和块层

应用程序和文件系统通过块层来访问块设备。图 2 显示了 Linux 块层以及与之交互的其它组件。从顶部开始,用户空间通过系统调用与内核进行互动。对文件系统的读写操作会传递到块层,形成文件系统 IO(FS IO)。此外,用户空间还可以通过导致页错误、脏页回写或换出等内存操作间接达到块层。 cgroup 子系统负责资源核算,并基于 cgroup 层级结构,在所有相关组件间传递控制信息。

块层使用 bio 数据结构来携带信息,如请求类型(例如读或写)、大小、目标设备、设备的扇区偏移、发出请求的 cgroup 以及数据复制源或复制目的内存。在请求提交给设备驱动程序之前,块层的控制和调度逻辑可以选择限制 bio 的速度,将它与其他请求合并等。Linux 内核提供了多种不同的 IO 调度器,可以被启用。我们将那些与 cgroup 子系统集成的调度器称为“控制器”,以此区别于仅仅确保整机有良好性能的普通 IO 调度器。


表 1:Linux IO 控制机制和功能

表 1 列出了各种 Linux IO 控制机制的特点。第 4 节通过实验全面比较了这些机制。在没有 cgroup 控制的情况下,IO 调度主要有三种选择:no scheduler、mq-deadline 和 kyber。这些选项并不向容器保证 IO 资源,而是确保一些总体性能特性,例如防止异步写入影响同步读取操作。

blk-throttle 允许通过设定每秒读/写 IOPS 或字节数的形式来限定 I/O 操作。然而,这些限制并不具备工作量保持,对于数据中心内多样化的设备和应用来说,配置起来十分困难。

BFQ 提供了比例控制 I/O 的工作量保持接口,但它忽略了与内存管理的交互,这可能导致隔离失效。此外,如第 4.1 节所示,BFQ 具有较高的每次请求开销和宽泛的延迟波动。最后,BFQ 根据每个容器读/写扇区进行轮询调度,这种方法在具有复杂内部操作的现代设备上效率低下。

除了 IOCost 之外,我们还开发了 IOLatency 控制器,它可以为单独的 cgroup 设置 I/O 延迟目标。具体而言,它界定了一个 cgroup 的 I/O 操作在其它 cgroup 受到限制前所能接受的最大延迟。例如,如果另一个设置了 5 毫秒延迟目标的 cgroup 其 I/O 操作开始超过 5 毫秒,那么一个延迟目标为 10 毫秒的 cgroup 将会被限流。我们已经将 IOLatency 控制器集成到上游 Linux 内核。

在实际生产部署中,我们发现了 IOLatency 存在的一些局限性。首先,基于延迟的接口只适用于严格的优先级划分,即阻止低优先级的工作负载干扰高优先级的工作负载,但缺乏比例控制使得它不适合在同等优先级的工作负载之间确保公平性。其次,尽管从技术角度讲,IOLatency 实现了工作量保持,但在多元化的设备和工作负载中寻找既能隔离又能工作量保持的配置几乎是不可能的。

2.3 硬件和工作负载异构性


图 3:Meta 设备群的设备异质性。

硬件异质性。硬件的逐步更新和供应链的多样化,导致了数据中心内部存在多种类型的 SSD。在 Meta 的服务器群中,图 3 显示了不同 SSD 设备的性能特征。图的左侧 y 轴表示随机和顺序读写操作的 IOPS,右侧 y 轴则显示了读写操作的延迟。我们运用 fio 工具来测量每款设备所能持续达到的峰值性能。

八种类型的固态硬盘(标记为 A 至 H)展现出各自独特的性能特征。具体而言,SSD H 在低延迟条件下实现了高 IOPS,SSD G 虽然 IOPS 较低,但同样保持了相对低的延迟,而 SSD A 则以中等的 IOPS 水平配以较高的延迟。每一款设备通常占数据中心总设备数量的比重不超过 14%,除了设备 F,它的占比达到了 19%。大约 20% 的 SSD 容量分布于图中未列出的 18 种设备,但这些设备的特性已经被图中显示的设备所涵盖。


图 4:IO 工作负载异构性

工作负载异构性。Meta 的应用程序表现出其 IO 工作负载的多样性。图 4 显示了 Meta 上几个典型工作负载的 I/O 需求。通过测量一周生产数据的 P50,我们观察到每秒读、写操作与随机、顺序字节操作之间的对比。像 Web A 和 Web B 这样的工作负载最能代表 Meta 的平均状况,它们的读、写操作在随机、顺序操作上大致均衡。而 Meta 的 Serverless 工作负载则高度过载,呈现出混合的读、写比例。Cache A 和 Cache B 是内存缓存服务,它们使用高速的块设备作为内存缓存的后端存储,这两者均展现出大量的顺序 I/O。此外,Meta 的非存储服务进行的显式 I/O 操作相对较少,它们的 I/O 大多来源于页面调度和周期性的软件更新。

总而言之,有效的 I/O 控制的重大挑战在于,在不需要每工作负载配置(例如延迟、IOPS 或每秒字节数)的情况下,能够应对硬件异构和工作负载多样化的稳健性,这通常在生产环境中太脆弱且难以管理。一个理想的 I/O 控制机制应当能够满足各类工作负载的复合需求,同时避免配置的爆炸式增长。

3. IOCost 设计

IOCost 的目标是实现 IO 控制,该控制需考虑到硬件设备的异构性和工作负载需求的多样性,同时为容器间提供比例分配的资源和强大的隔离性。

3.1 概述

IOCost 显式地将设备配置与工作负载配置解耦。对于每个设备,IOCost 引入了一个成本模型及一组服务质量(QoS)参数,它们定义并规范了设备的行为。而对于工作负载,IOCost 利用 cgroup 权重进行比例配置,这意味着工作负载的配置可以独立于设备细节,这在异构环境中大大简化并增强了大规模配置的便捷性和稳健性。

IOCost 采用多核 CPU 的分层加权公平调度概念。IOCost 通过每 IO 的成本建模来估算单次 IO 操作的占用情况,然后根据为每个 cgroup 分配的权重,使用该占用估算值来做调度决策。我们的创新设计将低延迟问题路径与周期性规划路径分开,使得 IOCost 能够扩展到每秒数百万次 IOPS 的 SSD。


图 5:IOCost 架构概览,显示了左侧如何评估 bio(块 I/O 请求)的成本以作出限流决策,以及右侧的离线成本模型与逻辑生成过程。

图 5 给出了 IOCost 体系结构的概览。IOCost 在逻辑上分为 问题路径(Issue Path)规划路径(Planning Path) 两部分,前者是运行在微秒时间尺度上的每 bio 操作,后者则是运行在毫秒时间尺度上的周期性操作。此外,离线工作用于推导出设备的成本模型和 QoS 参数。

让我们简短地探讨一下 bio 的生命周期及其与 IOCost 的交互过程。首先,在步骤 1 中,IOCost 接收到一个描述 IO 操作的 bio。随后的步骤中,IOCost 会计算这个 bio 的 cost,并作出相应的限流决策。

在步骤 2 里,IOCost 从 bio 中抽取特征,并利用成本模型参数计算出 𝑎𝑏𝑠𝑜𝑙𝑢𝑡𝑒 𝑐𝑜𝑠𝑡cost 是以时间单位表示的,但是一个 IO 操作的 cost 其实是一个占用率指标,而非延迟。例如,20 毫秒的代价意味着设备每秒可以处理 50 个这样的请求,但这并不说明每个操作实际耗时多久。我们将在第 3.2 节中进一步讨论特征选择和成本模型的细节。

紧接着,在步骤 3 中,绝对的 IO 成本会被除以发出请求的控制组(cgroup)的层次权重(hweight),以得出相对的 IO 成本。hweight 是通过在 cgroup 层次结构中向上递归,累计该 cgroup 相对于其同级 cgroup 所占的权重份额来计算的。hweight 代表着该 cgroup 有权获得的 IO 设备最终份额。例如,一个 hweight 为 0.2 的 cgroup 就拥有设备 20% 的份额,而一个 IO 操作的相对成本就是

步骤 4 显示了全局虚拟时间(vtime)时钟,它以虚拟时间速率(vrate)指定的速度与实际时间同步前进。每个 cgroup 跟踪其本地 vtime,每当发生一次 IO 操作时,本地 vtime 会根据该 IO 的相对成本向前推进。接着,在步骤 5 中,基于本地 vtime 与全局 vtime 之间的差距,IOCost 做出限流决策。这个差距代表了 cgroup 当前的 IO 预算。如果预算等于或大于某个 IO 的相对成本,该 IO 立即执行。否则,IO 必须等待直到全局 vtime 推进足够远。

在规划路径中,IOCost 收集 cgroup 的使用情况和完成延迟,并定期调整 IO 控制策略。在步骤 6 中,IOCost 根据设备反馈全局调整 vrate,进而调整总的 IO 发起量。由于模型可能过高或过低估计实际设备占用,vrate 的调整确保设备的良好利用。关于 vrate 调整和 QoS 的更多讨论见第 3.3 节。接下来,在步骤 7 中,IOCost 的捐赠算法高效地将多余的预算捐赠给其他 cgroup,实现工作量保持。第 3.6 节详细介绍了该算法。

在步骤 8 中,离线状态下,IOCost 利用部署设备上的性能剖析、基准测试和训练来构建每个设备模型的成本模型和 QoS 参数,这些参数在生产部署期间会被使用。

3.1.1 问题路径

问题路径决定了 IO 的 costhweight、基于本地和全局 vtime 的可用预算,并作出限流决策。

bio 的绝对成本是通过将成本模型应用于 bio 的特征来计算的。每个 cgroup 也被分配了一个权重,这个权重表示了该 cgroup 在其同级 cgroup 中所占的 IO 占用比例。为了避免在热点路径上重复递归操作,权重被合并并平展为 hweight,然后被缓存起来,只有当权重发生变化时才会重新计算。

一个没有发出 IO、因而没有消耗其预算的 cgroup 会导致设备利用率低下。为了解决这个问题,IOCost 区分了活跃的 cgroup。当一个 cgroup 发出 IO 时,它就变成了活跃状态;而在一个完整的规划周期过去而没有任何 IO 的情况下,它会变成非活跃状态。在计算 hweight 时,非活跃的 cgroup 会被忽略。这个低开销机制使得设备保持较高的利用率,因为闲置的 cgroup 隐式地将其预算捐赠给了活跃的 cgroup。当一个 cgroup 变为活跃或非活跃时,它会增加一个权重树生成号,以此指示权重已被调整。随后通过问题路径执行的 cgroup 会注意到这一点,并重新计算它们的 hweight

3.1.2 规划路径

规划路径负责全局协调,确保每个 cgroup 仅凭本地信息就能高效运行,并且能够收敛到期望的分层加权公平 IO 分配。它基于延迟目标的倍数定期运行,这样既能包含足够数量的 IO,又能允许精细的控制。

规划路径统计每个 cgroup 正在使用的 IO 量,以此确定它们可以捐赠多少权重,并相应地调整权重。通过预算捐赠,IOCost 实现了工作量保持,同时保证问题路径操作严格局限在 cgroup 本地。与捐赠相关的问题路径操作仅限于当预算紧张时减少或取消捐赠,这也是一个本地操作。

此外,规划路径还监控设备行为,并通过调整 vrate 来控制全局虚拟时间相对于实际时间的快慢,从而调节所有 cgroup 能发出的 IO 总量。例如,如果 vrate 设置为 150%,那么全局虚拟时间将以实际时间的 1.5 倍速度运行,并产生比设备成本模型指定的多 1.5 倍的 IO 预算。vrate 调整的条件和范围是由系统管理员通过 QoS 参数配置的。

3.2 设备成本建模

IOCost 将设备成本建模与运行时的 IO 控制分离。成本模型在部署前为每个设备离线生成。为了达到最大的灵活性,IOCost 允许成本模型以任意的 eBPF 程序形式表达。此外,IOCost 原生支持线性模型,其工作原理如下。IOCost 从 bio 请求中提取以下特征:1)读取或写入、2)相对于 cgroup 的上一次 IO 是随机还是顺序,3)请求的大小。IO 成本计算如下:

根据读/写和随机/顺序的组合,从四种 base cost 选择一种。根据读或写选择 size cost rate。因此,线性模型由六个参数组成:四种 base cost 和两种 size cost rate

为了方便起见,配置以不同的格式接受这六个参数——读写每秒字节数(bps),以及读写时的每秒 4kB 顺序和随机 IO(IOPS)。这些参数在内部被转换为 base costsize cost rate,转换公式如下:


图 6:IOCost 配置示例

图 6 显示了一个示例配置。对于读取操作,转化为每字节 2.05 纳秒的 size cost rate,顺序 base cost 为 104 微秒,随机 base cost 为 109 微秒。相应地,一个 32KB 的随机读取 bio 请求的成本将是 ,并且设备每秒钟能够处理 2840 个这样的请求。

我们的工具使用 fio 和饱和工作负载来推断设备的线性模型参数,例如,通过尽可能多地发出 4KB 随机读取请求来确定随机读取的 base cost。即使在 Meta 数据中心中存在大约三十种不同的存储设备,以这种方式系统地对设备进行建模仍然是可行的。我们已经将我们的建模工具集成到了 Linux 内核源码树。

3.3 QoS 和动态 vrate 调整

简单的线性建模无法捕捉现代 SSD 的复杂性。这些设备有着复杂的缓存层、请求重排序、垃圾回收机制,面对不同的 I/O 混合模式时,其表现往往出乎预料。先前的研究着重强调了精确建模 SSD 行为的难度。IOCost 通过动态调整 vrate 来应对设备性能的波动。

vrate 调整基于两个信号:I/O 预算不足和设备饱和。前者表明内核本可以发出更多 I/O,但由于由 vtime 决定的全局预算限制而无法做到。后者则表明设备无法处理更多的 I/O。如果系统能够发出更多 I/O 且设备并未饱和,vrate 将向上调整。反之,若设备处于饱和状态,vrate 则向下调整。

IOCost 通过追踪请求耗尽和延迟目标超限来识别设备是否饱和。当正在进行的 I/O 请求过多,耗尽了可用的 I/O 槽位,导致设备层出现长队列时,即发生请求耗尽。延迟目标是通过 QoS 参数设定的。例如,系统管理员可以配置,如果 90 百分位的读取完成延迟超过 10 毫秒,则认为设备处于饱和状态。

通过限制向设备发出的总 I/O 量,即便是在表现出突发行为或设备模型难以完全捕捉的其他行为的设备上,IOCost 也能实现一致的延迟。IOCost 将延迟视为设备层面的属性。它使用 QoS 参数来调控设备行为,然后分配由此产生的 I/O 占用。这种分离简化了工作负载的配置,并且对于保持 QoS 目标是必要的。理论上,如果我们放宽对批处理工作负载的设备限流,可能会失去对设备的控制,在延迟敏感型工作负载激活时,无法达到 QoS 目标。

3.4 使用 ResourceControlBench 调整 QoS 参数

QoS 参数决定了设备的整体限流,这是在设备利用率与一致延迟之间的重要权衡。最终,如何做出这种权衡取决于存储的使用场景。在 Meta,主要的考量是确保在竞争情况下合理的 I/O 延迟,而原始吞吐量则作为次要考量。

为了确保设备能够得到充分的限流,我们为 Meta 机群中的每个设备开发了一套系统性的方法来确定 QoS 参数。虽然完整描述超出了本文的范畴,但我们提供了一个简化的说明。

我们开发了 ResourceControlBench,这是一个高度可配置的复合工作负载,模仿了 Meta 中延迟敏感服务的行为。我们通过观察 ResourceControlBench 在不同 vrate 范围内的行为,利用它来进行 QoS 调整。我们在两种场景下执行 ResourceControlBench。

首先,ResourceControlBench 独占机器运行,并调整其工作集大小,直到可用于分页和交换操作的吞吐量开始限制 ResourceControlBench 的性能。随着 vrate 的降低,工作集大小也会下降。其次,ResourceControlBench 与另一个容器中的内存泄露一起运行。随着 vrate 的降低,I/O 控制得到改善,直到 ResourceControlBench 的延迟得到了充分保护,免受内存泄露导致的抖动影响。

这两种场景确定了 vrate 范围的两点,低于这两点不再需要进一步的 I/O 控制改进,高于这两点吞吐量的提升对于内存超额分配并没有带来实质性的好处。我们将每个设备的 vrate 设定在这两点之间。这些 QoS 参数被部署到机群中的每个设备上,从而实现了所有应用程序的一致延迟控制,并将吞吐量损失降至最低。ResourceControlBench 和场景生成工具作为开源软件提供。

3.5 处置优先级反转

考虑两个具有相同权重的 cgroup,A 和 B,在同一台机器上运行。A 持续泄露内存。当机器内存不足时,B 尝试分配内存并进入内存回收流程,此时会识别出 A 的一部分内存用于换出。由于这部分被换出的内存属于 A,因此换出 bio 的成本只能合理地归咎于 A。如果这个成本被记在 B 上,B 将因为 A 的过度内存使用而受到惩罚,破坏了资源隔离。

为使 B 完成内存分配,这个换出操作必须同步完成。如果 A 超过了其预算,对其进行限速会导致优先级反转,即 B 再次因为 A 的内存过度使用而受罚。IOCost 解决这个问题的方法是允许 A 产生“债务”,并在不进行限速的情况下发出 I/O 操作。A 的未来 bio 将按比例进行限速,直到用未来的预算还清债务为止。

然而,如果 A 泄露内存但没有发出可以被限速的 I/O,A 将获得不公平的大量“免费”换出 I/O,并且永远无法偿还债务。为解决这个问题,IOCost 在每次返回用户空间前添加了一个检查。如果累积的债务超过了阈值,线程会在返回用户空间前短暂阻塞,以此来限制“免费”I/O 的生成。结果,生成交换的内存活动被限速,而不会造成优先级反转。同样的机制也用于共享文件系统等操作,如日志记录。

3.6 预算捐赠

单个 cgroup 并不总是会发出达到 hweight 的 I/O 请求量。为了工作量保持,IOCost 通过动态降低捐赠者 cgroup 的权重,允许其他 cgroup 利用存储设备。我们研究了多种方案,包括暂时加速 vrate,但发现只有局部调整权重的策略能够满足以下所有要求:1)I/O 问题路径保持低开销;2)发出的 I/O 总量不会超过 vrate 设定的限制;3)捐赠者可随时低成本撤销捐赠。

每个规划阶段都会识别出捐赠者,并计算出它们能捐出多少 hweight。随后,它会计算出捐赠 hweight 后已降低的权重。权重计算的过程设计得让父节点的权重调整完全由子节点的权重变化决定。

由于捐赠是通过权重调整实现的,所以 I/O 问题路径不会发生变化,也不会与设备级别的行为交互,这样就满足了前两项要求。捐赠者只需更新自己的权重,并沿问题路径向上传播更新,无需任何全局操作即可撤销捐赠,从而满足了最后的要求。这会增加权重树的代数,后续的 I/O 发出者会重新计算它们的 hweight


图 7:规划阶段(a)、规划阶段之后(b)和问题路径期间(c)的预算捐赠示例

高层次捐赠示例。 在图 7(a) 中,容器 A 和 B 的权重分别是 。在规划阶段,发现 B 未使用其一半的预算。为了避免设备利用率低下,系统将 B 原始预算的一半转移给了 A。图 7(b) 显示了这一变动对第二周期的影响。随着 hweight 增加,A 的 I/O 相对成本降低,可以更频繁地发出,而 B 则达到其降低后的新预算上限。在周期末尾,不再需要进一步调整。图 7(c) 显示,在第三个周期中期,B 尝试发出更多 I/O,并在问题路径中撤销捐赠,无需等待下一个规划阶段。值得注意的是,容器也可以只撤销其原始捐赠的一部分。

权重树更新算法。 表示权重, 表示兄弟节点的权重总和, 表示 hweight,而 则代表子树中所有捐赠叶节点的 hweight 总和。下标 标记父节点,而撇号(’)表示捐赠后的数值。


图 8:B 和 H 捐赠了部分预算

图 8 显示了预算捐赠过程。在这个例子中,叶节点 B 和 H 的活跃使用量总共比它们设定的 hweight 少 0.25。过剩的部分被捐赠给其他可以按照它们的层级权重比例使用更多 I/O 的 cgroup。最重要的是,只需要局部更新,因为 的值仅沿着从 B 和 H 到根节点的路径递减,之后所有其它节点都可以在问题路径中懒惰地计算出它们新的 hweight

捐赠值 会沿着树向上传播,作为预算捐赠算法的输入。仅需沿着从根到捐赠子节点 B、D 和 H 的路径计算更新后的权重 和层级权重 。为了确保非捐赠节点不需要更新,我们维持了两个不变性质,进而推导出更新后的层级权重 、兄弟节点的权重总和 和更新后的权重

第一个不变性质强制规定了父节点的非捐赠权重占比在预算捐赠后不会改变。

第二个不变性质保证了所有未捐赠的兄弟节点的汇总权重 在预算捐赠后不会发生变化。

步骤

(1) 使用公式 (4) 的约束从父节点层级权重 值计算新的 hweight:
(2) 基于公式 (5) 的约束计算新的兄弟节点的权重总和:
(3) 最终的权重由计算出的 得出:

在其他节点上完整展示剩余的 值是为了完整性,但在预算捐赠的过程中,这些值并非必要。值得注意的是,对于其他节点而言, 的值并不会改变。这种效率对于庞大的 cgroup 层次结构非常重要。仅需沿着从捐赠叶子节点到根节点的路径更新 ,所有其他节点基于这些 更新的新 值就会获得正确的数值。举例说明,在这个例子中,B 和 H 共释放了 0.25 的 hweight,根据 E、F 和 G 最初的 hweight 比例 0.16:0.04:0.35 分配,从而分别向 E、F 和 G 捐赠 0.07、0.02 和 0.16 的 hweight

4. 评估

本节表明 IOCost 提供了一种低开销、工作量保持、内存管理感知并允许进行比例 cgroup 配置的 I/O 控制。我们将 IOCost 与最新的 Linux I/O 控制机制以及我们在第 2.2 节描述的先前解决方案 IOLatency 进行了比较。显示没有一种机制能够与 IOCost 拥有的特性和性能相媲美。

在所有的实验中,除非另有说明,我们使用的是单插槽、64GB 内存的服务器,配备三种不同的 SSD:1)较早一代的商用 SSD;2)较新一代的商用 SSD;3)高端企业级 SSD。 我们安装了 5.6 版的 Linux 内核,该内核已经应用了来自 5.15 版本最新的 IOCost 变更。模型参数是通过使用 fio 饱和工作负载确定的,如第 3.2 节所述。QoS 参数是通过使用 ResourceControlBench 确定的,如第 3.4 节所述。

4.1 低开销

在数据中心中控制高速 SSD 的 IO 操作,需要控制器具有极小的开销。本次实验采用了一款最大读取 IOPS 为 75 万次的 SSD,我们使用 fio 工具生成尽可能多的 4KB 随机读取,以测试 IO 子系统能支持的最大数量。


图 9:IO 控制开销

图 9 测量了启用 IO 控制时,使用多种不同机制所能达到的最大 IOPS。控制器或调度程序并未设置进行实际的限速,这样我们可以测量在快速 IO 问题路径上引入的开销。我们使用企业级 SSD 进行此实验,以展示我们最快存储设备上的开销。我们禁用了所有控制器的 QoS 设置,以便单纯测量在不限制设备时,各控制器的基线开销。

none 列对应于没有运行任何软件调度器或控制器的情况,展示了该设备上块层可实现的吞吐量。mq-deadline 是 Linux 默认的调度器,具有适度的开销。kyber 的表现与没有调度器时无异。这两种 IO 调度器都不提供 cgroup 控制功能,因为它们只提供系统级的调度。bfq 则有严重的软件开销。尽管我们进行了大量调优,但始终未能找到合理性能的配置。其余的列表明,其他 IO 控制器并没有增加明显的开销。虽然 IOCost 的限速逻辑比其他控制器复杂得多,但由于其将问题拆分为快速的 IO 问题路径和较慢的规划路径,因此能够确保几乎无感的开销。

4.2 比例控制和工作量保持

工作量保持 IO 控制对于确保在某些消费者空闲时,存储设备的性能得到充分利用至关重要。如果没有工作量保持 IO 控制,我们就需要为诸如操作系统软件更新等不频繁的活动过度预置 IO 资源。

为了评估这些特性,我们进行了两个相关的实验,其中两个合成的工作负载同时运行。在第一次实验中,我们运行了两个延迟敏感型工作负载实例,在 p50 延迟低于 200 微秒的情况下,持续发出 4KB 的随机读取请求。这些工作负载模拟了在线服务,如果请求延迟过高可能会导致负载卸载。我们将高优先级工作负载的 IO 配置为低优先级工作负载的两倍。这个实验是在我们较旧一代的 SSD 上进行的,由于它的相对较低的延迟,对 IO 控制的要求更高。


图 10:比例控制。高优先级和低优先级工作负载接收的 IOPS 目标比例为 2

图 10 显示了第一次实验的结果。我们仅关注 cgroup 感知的 IO 控制机制。bfq 被配置了期望 2:1 权重比例。然而,高优先级工作负载以超过 10:1 的比例占据主导地位。这是因为低优先级工作负载受到较差的延迟影响,并持续降低其 IO 发出率以保持在 200 微秒的目标之下,这反过来又让高优先级工作负载得以占据主导并接收远超其应得份额的 IO。blk-throttle 被配置为限制每个工作负载以保持 2:1 的比例。它的表现符合预期,与 IOCost 观察到的延迟匹配。IOLatency 没有提供配置此类分布的方法。相反,我们尝试通过调整每个 cgroup 的延迟目标来实现期望的分布,但最佳配置(如图所示)仍然导致大约 10:1 的分布。最后,IOCost 如同 bfq 一样配置权重,并且能够精确匹配预期的 2:1 比例。

第二次实验保持了相同的配置,只是将高优先级工作负载替换为一个顺序执行、思考时间为 100 微秒、随机 4KB 读取操作的工作负载,即在上一次 I/O 完成后 100 微秒才发起新的 I/O。这次实验所取得的吞吐量取决于读取操作的延迟,远低于之前的实验。因此,低优先级工作负载可利用的吞吐量取决于 I/O 控制器的工作量保持特性。我们预期低优先级工作负载会耗尽剩余的可用 I/O。


图 11:工作量保持。低优先级的工作负载应该用尽所有可用容量

图 11 显示了第二次实验的结果。bfq 的工作量保持特性导致低优先级工作负载完成了大量的 I/O 操作。bfq 在这方面超越其他机制的能力,源于其较弱的延迟控制,这反而导致高优先级工作负载表现明显恶化。高优先级工作负载平均延迟为 250 微秒,标准差接近 1 毫秒,而其他所有机制都能将延迟保持在平均 200 微秒以下,标准差约为 200 微秒。这个实验也揭示了非工作量保持方法的主要缺点,例如 blk-throttle,它能很好地控制延迟,但不允许低优先级工作负载消耗比前一次实验更多的 IO。IOLatency 和 IOCost 表现相当,既能控制高优先级工作负载的延迟,又允许低优先级工作负载消耗原本可用的 I/O。这两个实验共同证明了 IOCost 独特地提供了比例和工作量保持的 I/O 控制。

4.3 机械硬盘建模

尽管 SSD 构成了 Meta 数据中心的绝大多数,IOCost 同样适用于机械硬盘。与 SSD 不同,机械硬盘具有较高的寻道延迟,这意味着随机 I/O 的吞吐量比顺序 I/O 低(或者说是更高的占用成本)。我们进行了一项实验,其中两个工作负载分别发出随机 4KB 读取或顺序 4KB 读取。其中一个工作负载(高权重)被配置为另一个工作负载(低权重)权重的两倍。我们比较了 mq-deadline,bfq 和 IOCost 在三种情况下的表现:两个工作负载都发出随机读取(rand/rand),高优先级工作负载发出随机读取而低优先级发出顺序读取(rand/seq),以及两者都发出顺序读取(seq/seq)。


图 12:机械硬盘上随机和顺序工作负载的公平性

图 12 显示了这次实验的结果。为了清晰地展示差异,我们将随机和顺序工作负载的吞吐量分别标准化为设备能够处理的每种类型工作负载的峰值吞吐量。结果显示,mq-deadline 无法在任何工作负载上以 2:1 的比例提供公平性,因为它只是一个全局调度器。BFQ 在两个工作负载都发出顺序 I/O 时表现出色,保持了预期的 2:1 比例,但在两个工作负载都发出随机 I/O 时遇到困难,尤其是在混合了顺序工作负载时,它过分分配了设备给随机读取工作负载。相比之下,IOCost 通过建模随机 I/O 与顺序 I/O 的成本差异,并确保在设备占用方面实现公平性,在所有情况下都保持了预期的 2:1 比例。这导致了适当的隔离,即无论邻居的磁盘访问模式如何,工作负载从磁盘收到的服务都是相同的。

4.4 QoS 和 Vrate 调整

正如第 3.3 节讨论的那样,现代 SSD 的复杂性使得简单的建模方法不够准确,可能会导致 IOCost 对设备的占用率估计偏低或偏高。vrate 通过动态调整整体的 I/O 发出速率来补偿这种建模的不准确性。


图 13:由于模型不准确而进行的 vrate 调整

图 13 显示了我们在新一代商用 SSD 上进行的一个实验结果,其中一项工作负载试图通过 4KB 的随机读取来饱和设备,而 QoS 设置被配置成使 IOCost 保持 90 百分位的读取延迟在 250 微秒。最初,vrate 保持在约 100 左右,这表明模型参数适合维持这样的 QoS。

在第一个指示的时间点,我们在线更新模型参数,将其值减半(实际上是在声称设备的占用量仅为之前的一半)。作为响应,读取速率下降。然而,vrate 迅速攀升至大约两倍的发送速率,同时保持着我们期望的 QoS。最后,在第二个指示的时间点,我们再次在线更新模型参数,将其设置为原来值的两倍(实际上是在声称设备的占用量是之前的两倍)。起初,发送速率过度饱和设备,导致延迟出现尖峰,但随着 vrate 降至大约初始值的一半,延迟开始稳定下来,以维持 QoS。这项实验表明,IOCost 中动态 vrate 调整功能能够处理建模不准确的问题,同时仍能保持 QoS。

4.5 内存管理感知

在数据中心中,资源过度分配是一种普遍用来提高利用率的方法。通常的做法是部署一个拥有保证资源的高优先级工作负载,并允许低优先级的工作负载尽力而为地消耗机器上的剩余资源。内存管理的集成对于确保资源得到恰当回收至关重要。


图 14:延迟敏感型工作负载与内存泄漏工作负载堆叠时的每秒请求数 (RPS)

我们展示了 Meta 生产网络服务器在老一代和新一代商用固态硬盘上的结果。我们在系统切片中启动了一个内存泄漏进程(参见图 1 以了解 cgroup 层级结构),这个进程最终会被 out-of-memory(OOM)杀手终止。图 14 显示,由于内存争抢,Web 服务器的吞吐量降低了。在理想资源控制条件下,Web 服务器应该主要保持其吞吐量。mq-deadline 隔离效果不佳,因为它缺乏 cgroup 集成,但与高端 SSD 配合时稍好,仅仅是因为高端 SSD 具有更大的带宽。尽管 BFQ 具有比例控制,但它的表现最差,导致吞吐量几乎完全丧失,这是由于缺乏延迟控制和内存管理集成。IOLatency 表现中等。最后,IOCost 超越了所有其他 I/O 控制机制,Web 服务器的吞吐量不低于正常水平的 80%。

为了评估内存管理集成的特定细节,我们设计了一个实验,其中 ResourceControlBench 与 stress 同地运行,stress 是一个复合内存消费者,它不断地访问其配置的工作集。我们配置了一个 PID 控制器,逐渐将 ResourceControlBench 的负载从其峰值计算负载的 40% 增加到 80%,同时保持 95 百分位的延迟在 75 毫秒以下。随着 ResourceControlBench 负载的增加,其内存访问频率增加,推动了对其驻留内存需求的增长。相应地,为了确保高优先级的 ResourceControlBench 有足够的内存,复合内存消费者的内存必须被换出。我们测量了 ResourceControlBench 从其峰值负载的 40% 扩展到 80% 所需的时间。


图 15:过度承诺环境中的启动时间

图 15 显示了这个实验的结果。没有 stress 的两个基准配置显示,IOCost 的加载时间大约是 BFQ 的一半。当 stress 消耗内存时,IOCost 配置能够比 BFQ 快约 5 倍地完成纵向扩展。我们还运行了 IOCost 的修改版本。在第一个配置中,所有交换出的 I/O 都被计费到根 cgroup,因此永远不会受到限制。stress 无论消耗多少交换 I/O,都能自由运行。在第二个配置中,我们根据来源的 cgroup 来限制交换 I/O,这会产生优先级反转,即在交换出 stress 的内存时,ResourceControlBench 可能受到限制。这两种配置的表现不如生产版本的 IOCost,这表明 IOCost 的债务机制(第 3.5 节)如何避免优先级反转,同时保持良好的 I/O 控制。

4.6 堆叠延迟敏感的工作负载

IOCost 在生产环境中的一个应用是确保多个容器能够获得其应有的 I/O 服务份额。在 Meta,我们运行着类似 Zookeeper 的工作负载,它提供了一个强一致性 API,用于配置、元数据和协调原语,如监视器、锁和信号量。单个操作复制到多个参与者,以提供容错能力。在 500000 次事务后,该服务会触发内存数据库的快照,即使在正常负载下,也会导致瞬时的写入峰值。生产服务对读写操作有一秒的服务等级目标(SLO)。这一 SLO 使得该服务与其他服务共存变得困难,因为集合中的一个参与者遇到的减速可能导致整个操作放慢。这项服务运行在配备了我们企业级 SSD 的机器上。

我们分析了这样一个场景下的服务行为:十二个集群 (每个集群由五个参与者组成) 分布在五台机器上。同一集群中的任意两个参与者都不会共享主机。这种配置允许多个低流量集群共享机器,实现合理的总利用率。


图 16:不同 IO 控制方法对 ZooKeeper 延迟 SLO 超限的影响

这十二个集群各自接收中等程度的流量,即每秒 3000 次读取和 100 次写入。其中十一个集群的平均有效载荷大小为 100KB,而第十二个集群作为一个“嘈杂的邻居”,其有效载荷大小为 300KB。图 16 显示了十一个表现良好的集群的 P99 延迟。SLO 超限由其频率和严重程度来表征。在六小时的实验期间,使用 blk-throttle、BFQ 和 IOLatency 时,这些集群反复超限 1 秒的 SLO。具体来说,blk-throttle 显示了 78 次超限,其中有些持续了数十秒。BFQ 显示了 13 次超限,每次持续 2-5 秒。值得注意的是,虽然图中没有显示,但由于 BFQ 限流的严重性导致系统完全无响应,我们不得不多次运行这个实验。IOLatency 无法配置为比例控制,也显示出不良行为,即 31 次超限,最长的一次持续了 7.8 秒。而使用 IOCost 时,有效地隔离了“嘈杂邻居”集群和快照的影响,仅出现了两次轻微超限,持续时间分别为 1.5 秒和 1.04 秒。

4.7 远程存储和 VM 环境

除了本地存储之外,IOCost 还适用于为远程块存储环境提供 I/O 控制,比如公共云中常见的环境。为了评估 IOCost 的广泛适用性,我们重复了图 14 中的实验,不过这一次将 Meta 的生产 Web 服务器替换为与一个在低优先级 cgroup 中运行的、高速内存泄漏程序并行运行的 ResourceControlBench。


图 17:AWS EBS 和 Google Cloud Persistent Storage 中延迟敏感型工作负载与内存泄漏工作负载叠加时的每秒请求数 (RPS)

我们在公共云的 VM 中运行这两个工作负载,虚拟机的客户操作系统配置了 IOCost。图 17 显示了四种配置的保护比率——两种 AWS Elastic Block Store (gp3-3000iops, io2-64000iops),以及两种 Google Cloud Persistent Disk (balanced, SSD)。尽管不同延迟配置文件存在差异,实验清楚地表明,无论是在本地还是远程挂载的情况下,IOCost 都能够有效地隔离所有配置的 I/O。这个实验证明了 IOCost 在建模和 QoS 参数化方面的稳健性,可以成功应用于 Meta 之外的环境。

4.8 包获取和容器清理

IOCost 相较于 IOLatency 的一个重大特性是,其比例控制能力使我们能够确保系统服务和工作负载获得公平的 I/O 份额,而不是强制执行严格的优先级排序。此外,即便在极端情况下,当服务器资源被充分利用且竞争激烈时,IOCost 仍能成功地保护服务和工作负载。

包获取失败。 在 Meta,一个常见的操作是为容器获取包。这一过程通过一个 host critical 服务(容器代理)请求系统服务来获取包。我们经常遇到由于系统服务因 I/O 资源不足而无法响应,导致两者之间的通信失败的情况。包获取失败会导致容器更新失败,进而常常导致整台机器不得不退出生产环境。


图 18:随着区域从之前的解决方案 IOLatency 迁移到 IOCost,包获取失败率降低

图 18 显示了 IOCost 的效果,当数以十万计服务器组成的区域在两个月的时间里从 IOLatency 迁移到 IOCost。随着 IOCost 的启用,该区域内的包获取错误率显著下降,错误数量大约减少了 10 倍。

容器清理失败。 在 Meta 的数据中心,定期进行的操作之一是清理老旧容器。我们依赖 btrfs 及其写时复制语义,因此这通常是一个低成本的操作,但我们仍然会遇到一些情况,这些操作可能需要几秒钟。这种情况往往是主工作负载耗尽容器代理的 I/O 资源所造成的。清理老旧容器通常是为了确保后续容器有足够的磁盘空间,而清理失败可能导致机器在功能上变得不可用。


图 19:随着区域从之前的解决方案 IOLatency 迁移到 IOCost,容器清理失败次数减少

图 19 显示了容器清理失败率的减少,即那些耗时超过 5 秒的清理操作,在该区域迁移至 IOCost 后的变化。IOCost 的效果立竿见影。具体来说,我们看到 IOCost 实现了 3 倍的减少,极大地降低了停滞情况。这再次表明了 IOCost 对容器编排系统成功管理主机能力的影响。

5. 经验教训

Meta 拥有全球最大的 I/O 控制部署之一。最初的动机之一是解决因系统服务内存泄漏导致的隔离失效问题。单独的内存控制是不够的,因为即使设置了内存限制,仍然会导致回收过程,影响延迟敏感应用程序的 IO。只有将内存控制和 I/O 控制结合在一起,我们才能实现全面的隔离。

我们尝试了现有的 I/O 控制机制,但发现它们对 Meta 的异构设备和应用程序无效。通过 blk-throttle 为每个应用程序配置 I/O 限制效率低下,容易出错,最终难以处理。BFQ 显示了显著的开销和宽泛的延迟波动,并且在实际场景中无法实现有效的隔离。

我们首先开发了 IOLatency,它揭示了在内存管理和文件系统操作中的优先级反转导致的隔离失效问题。解决了这些优先级反转问题后,我们能够通过调整延迟目标来实现全面的隔离。然而,生产环境下的配置非常困难,因为延迟目标是异构设备属性和动态应用程序属性构成的复杂函数。针对某一场景优化的配置往往对其他场景无效。此外,它无法在具有相同优先级的多个竞争性应用程序之间进行 I/O 仲裁。

随后,我们开发了 IOCost 来解决 IOLatency 的局限性。IOCost 的配置更为简便,首先可以通过使用 fio(第 3.2 节)对设备性能进行建模,然后利用 ResourceControlBench(第 3.3 节)调整 QoS 参数,以此系统性地实现设备配置。有了每个设备的 I/O 成本模型,就可以通过简单的比例权重为各种应用程序实现有效的 I/O 控制,而无需针对每个应用程序进行离线性能剖析或配置 IOPS、字节数或延迟,这些方法通常过于脆弱和难以大规模生产使用。总体而言,IOCost 已在生产环境中稳健运行两年,有效应对了我们的设备群中异构设备和多样化的应用程序的场景。

倾向于性能一致的 SSD。 在 Meta 的数据中心,我们反复遭遇不可预测的 SSD 行为,并发现迎合特定设备的行为并不现实。随着各种应用程序在异构设备群中迁移,对我们来说,针对遇到的特定 SSD 的奇异情况来调整每个应用程序是不切实际的。我们放弃第一代解决方案 IOLatency 主要是因为它需要脆弱的逐应用程序调优。我们当前的解决方案利用 IOCost 的 QoS 特性来限制 SSD,以实现对多样化应用程序可接受的延迟和一致性。

总的来说,我们的经验表明,与那些具有短时、不可预测、高峰值性能的 SSD 相比,性能更一致的 SSD 可以在高度扩展和复杂的环境中得到更有效的利用。因此,我们建议吞吐量和延迟稳定的 SSD 更适合数据中心使用。

6. 相关工作

与我们的发现一致,The Tail at Store 对生产存储设备进行了大规模研究,发现在不同设备之间存在大量的性能差异。此外,FLIN 发现工作负载的 I/O 请求模式在并发执行的应用程序间的不公平性中扮演了重要角色。

ReFlex 采用了一种建模方法来考虑访问远程闪存设备时读写操作之间的相互影响。SSDcheck 为现代 SSD 构建了一个性能模型,以预测每个请求的延迟,并基于预期的请求延迟进行调度。类似地,SSD Performance Transparency 讨论了建模 SSD 性能的需求和挑战,并提倡对设备进行逆向工程,而非黑盒建模。

关于虚拟机监控器的文献致力于解决跨多个不同工作的 I/O 公平性问题。PARDAmClock 都探讨了为访问网络存储的 VM 提供粗粒度公平性的设计。VMWareNetApp 都提出了 I/O 解决方案,允许 VM 获得配置数量的 IOPS。相比之下,IOCost 应对了 I/O 子系统与内存子系统交互带来的额外挑战,并通过建模设备占用率,而非仅以 IOPS 或延迟来测量和控制,独特地实现了 I/O 公平性。我们认为,建模设备占用率可能是虚拟机监控器值得探索的一个富有成效的方法。

CelloArgonRedline 都提出了在慢速、机械硬盘时代控制 I/O 的方法,这类驱动器具有相对较低的并发性和较高的寻道延迟。最近,WDT 描述了一个基于权重配置的、cgroup 感知的 I/O 调度器,旨在针对高速 SSD,与 IOCost 不同的是,它分配的是 I/O 带宽而非占用率。FlashBlox 对 SSD 通道进行了分区,这允许硬件强制隔离,但代价是租户数量灵活性的降低。

Split-level I/O scheduling 中,作者指出了在调度时需要考虑 IO 栈不同层的信息。IOCost 识别了交换和日志记录的 I/O 源,并在不引起优先级反转的情况下引入了对内存管理和文件系统日志记录操作的 I/O 控制。

多篇文献 [8, 10–12,15, 24, 25, 31, 33] 聚焦于资源管理。这些解决方案旨在集中部署运行的应用程序间划分系统资源,同时不违反各自的 SLO。其他工作 [37, 38] 提出了针对容器的架构和操作系统扩展。总体而言,它们在 IOCost 之上或之下运作,并可以利用 IOCost 强大的 I/O 控制,进一步增强数据中心环境下集中部署运行的能力。

7. 结论

我们已经识别出在容器化环境中对 I/O 控制的需求。本文介绍了 IOCost,这是一种专为容器化环境设计的 I/O 控制方案,它为数据中心中异构存储设备和多样化工作负载提供了可扩展、工作量保持且低开销的 I/O 控制。我们的方法通过离线生成的设备成本模型来估算设备占用率。此外,IOCost 的设计将 I/O 控制分为轻量级的按 I/O 问题路径和周期性的 I/O 规划路径。一种创新的 cgroup 树层次权重更新算法,确保容器能够以最小的开销动态共享未使用的 I/O 预算。最后,我们分享了使用 IOCost 的经验以及潜在的未来硬件发展方向。

本文作者 : cyningsun
本文地址https://www.cyningsun.com/06-27-2024/iocost-block-io-control-for-containers-in-datacenters-cn.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!

# 数据库