支持卷本地缓存

https://blueprints.launchpad.net/cinder/+spec/support-volume-local-cache

该蓝图建议在 cinder 和 os-brick 中添加对卷本地缓存的支持。 缓存软件,例如 open-cas [5] 可以使用快速的 NVME SSD 或持久内存(配置为块设备模式)来缓存慢速远程卷。

问题描述

目前有不同类型的快速 NVME SSD,例如 Intel Optane SSD,延迟低至 10 微秒。 此外,旨在达到 SSD 尺寸但 DRAM 速度的持久内存现在越来越流行。 持久内存的典型延迟低至几百纳秒。 而虚拟机远程卷的典型延迟可能在毫秒级别(iscsi / rbd)。 因此,这些快速的 SSD 或持久内存可以本地安装在计算节点上,并用作远程卷的缓存。

为了实现缓存,需要更改三个项目。 这些是 cinder、os-brick 和 Nova。 同时,应在系统中添加相关的硬件,例如高性能 SSD 或持久内存。 该机制类似于卷加密,其中使用 dm-crypt。 缓存软件支持将添加到 os-brick 中,Nova 在尝试附加卷时会调用 os-brick,os-brick 然后调用缓存软件来为卷设置缓存。 之后,将创建一个新的虚拟块设备,并位于原始块设备之上。 os-brick 将此新的虚拟块设备暴露给 Nova,同时原始块设备的挂载点将保持不变。 所有缓存设置和拆卸都将在 os-brick 中处理。

将在卷类型的额外规格中添加一个属性。 如果卷类型具有“cacheable”的额外规格,则表示相关的卷可以在计算节点本地缓存。

与所有本地缓存解决方案一样,不支持多重附加。 这是因为节点 1 上的缓存不知道节点 2 对后端卷所做的更改。 因此,cinder 应该保证这两个属性不能同时设置。

考虑到虚拟机迁移,缓存软件不应格式化源卷(要缓存的卷)。 因此,不能使用 bcache 等缓存软件。 此规范仅支持 open-cas。 open-cas 使用起来很简单,您只需要指定一个块设备作为缓存设备,然后就可以使用该设备来缓存其他块设备。 这对上层和下层是透明的。 对于上层,客户机不知道它正在使用模拟的块设备。 对于下层,后端卷不知道它已被缓存,并且后端卷中的数据不会因为缓存而产生额外的更改。 这意味着即使缓存由于某种原因丢失,后端卷也可以安装到其他位置并立即可用。 open-cas 支持以下缓存模式

  • 写通 (WT):同时写入缓存和后端存储。 因此,数据在缓存和后端存储中完全同步。 当缓存失效时,不会有任何数据损坏。

  • 写回 (WA):类似于写通,但仅缓存已在缓存中的卷块。

  • 写失效 (WI):写入后端存储并使缓存失效

  • 写回 (WB):写入缓存并延迟写入后端存储。 这种模式具有更好的写入性能,但有可能丢失数据,因为最新的数据在缓存中,但可能未刷新到后端存储。

  • 只写 (WO):类似于写回,但仅缓存写入,不缓存读取。 因此,这种模式也有可能丢失数据。

前三种模式适用于必须保证数据完整性的场景,每个写入 I/O 都同时写入缓存和后端存储。 在这些模式下,它就像 ceph 中的只读缓存功能一样。 不会阻止任何操作。 例如,虚拟机实时迁移、快照拍摄、卷备份等可以像往常一样进行。 缓存软件也可以随时更改为其他软件,因为缓存中任何时候都没有脏数据。 这适用于读密集型场景,但对于写 I/O 没有好处,因为每个写 I/O 都需要每次都访问后端存储以确保数据完整性。

最后两种模式适用于对读/写性能都有很高要求,但对数据完整性要求较低的场景。 例如,某些测试环境。 在这两种模式下,依赖于后端卷包含完整数据的任何操作都将无法安全地工作。 因此,虚拟机实时迁移、快照、卷备份、一致性组等无法安全地工作,因为缓存中可能仍然存在脏数据。 至少,操作员需要通过某种方式停止磁盘 I/O 并刷新缓存(通过 casadm)才能执行这些操作。 缓存软件也不能更改为其他软件,除非所有脏数据都已刷新到后端。

可以通过缓存管理工具通过缓存实例 ID 获取缓存模式。 Nova 将可用的缓存实例 ID 列表传递给 os-brick,因此 os-brick 知道缓存实例的缓存模式。 此规范将使 os-brick 拒绝附加具有最后两种缓存模式的卷。 但是,缓存模式是在 OpenStack 之外设置的,Cinder / os-brick 无法控制在卷附加后缓存模式是否被修改。 但是,将明确记录写回 (WB) 和只写 (WO) 模式是危险的,并且操作员在尝试设置为这些缓存模式时应完全了解他们正在做什么。

某些存储后端可能支持 discard/TRIM 功能,但缓存软件对此没有意义。 缓存软件根据策略(例如“lru”(open-cas 的默认策略))驱逐数据。

某些存储客户端、计算节点可能为卷启用多路径,例如 iscsi/fc 卷。 卷本地缓存无法与多路径一起工作,原因与多重附加相同。 os-brick 通过函数 get_volume_paths() 检测多路径,并且不会为具有多路径的卷设置缓存。

对“cacheable”卷进行重定型不会引入任何限制。 这是因为重定型包括两个步骤:1) 附加新卷 2) 分离旧卷。 因此,旧卷将从缓存中释放,新卷将被缓存。

这是共享缓存,要缓存的卷的数量是无限的。 卷被缓存并不意味着它会在缓存中占用空间。 具有热 I/O 的卷将消耗更多的缓存空间,而没有 I/O 的卷将不会占用缓存设备中的任何空间。 当数据变冷或另一个卷的 I/O 变热时,缓存中的数据将被驱逐。

用例

  • 在读密集型场景中,例如 AI 训练,虚拟机卷本地缓存将显着提高存储性能(吞吐量,尤其是磁盘 I/O 延迟)

提议的变更

为了进行卷本地缓存

  • 在 Nova 中,最终用户选择一个被宣传为具有卷本地缓存的 flavor,以便客户机可以降落在具有缓存功能的服务器上。 最终用户应根据选择的服务器 flavor 期望不同的性能。 更多信息,例如错误处理、用户消息等,不在本规范的范围内,将在 Nova 规范 [4] 中定义。

  • 在 Cinder 中,应选择“cacheable”的卷类型。

Cinder 确定并设置“cacheable”属性。 标记为“cacheable”的卷并不意味着它必须被缓存,它只是意味着它有资格被缓存。 Nova 仅在卷具有“cacheable”属性时才调用 os-brick 来为卷设置缓存。

缓存模式绑定到缓存实例。 因此,不同的缓存实例可以具有不同的缓存模式。 如果它们由相同的缓存实例缓存,则所有缓存的卷共享相同的缓存模式。 操作员可以使用缓存软件管理工具动态更改缓存模式。 因此,缓存模式设置在 OpenStack 之外,不受 OpenStack 控制。 操作员不应更改为不安全的模式。 os-brick 仅接受来自 Nova 的缓存名称和缓存实例 ID。

cache_name 标识要使用的缓存软件,当前仅支持“opencas”。 Nova 知道缓存名称是什么,这取决于计算节点上启用了哪些缓存软件。

每个计算节点可以有多个缓存实例。 os-brick 可以通过缓存管理工具(casadm)根据例如总缓存大小、有多少可用空间等对每个传入的缓存实例进行加权,并选择最佳的一个。

某些存储类型支持从 cinder 端触发的“扩展卷”,例如通过命令“cinder extend ...”。 当卷未“使用中”时,它可以正常工作。 但是,如果卷已附加并且“使用中”,os-brick 将不支持扩展,并且仅对具有可缓存 volume_type 的卷引发 NotImplementedError。 这是因为 open-cas 在当前版本中不支持动态卷扩展。 但是,“调整实例大小”功能,该功能由 Nova 触发,仍然可以工作,因为在“调整实例大小”期间,卷将被分离然后重新附加。

最终解决方案将如下所示

                       Compute Node

+---------------------------------------------------------+
|                                                         |
|                        +-----+    +-----+    +-----+    |
|                        | VM1 |    | VM2 |    | VMn |    |
|                        +--+--+    +--+--+    +-----+    |
|                           |          |                  |
+---------------------------------------------------------+
|                           |          |                  |
| +---------+         +-----+----------+-------------+    |
| |  Nova   |         |          QEMU Virtio         |    |
| +-+-------+         +-----+----------+----------+--+    |
|   |                       |          |          |       |
|   | attach/detach         |          |          |       |
|   |                 +-----+----------+------+   |       |
| +-+-------+         | /dev/cas1  /dev/cas2  |   |       |
| | osbrick +---------+                       |   |       |
| +---------+ casadm  |        open cas       |   |       |
|                     +-+---+----------+------+   |       |
|                       |   |          |          |       |
|                       |   |          |          |       |         Storage
|              +--------+   |          |    +-----+----+  | rbd   +---------+
|              |            |          |    | /dev/sdd +----------+  Vol1   |
|              |            |          |    +----------+  |       +---------+
|        +-----+-----+      |          |                  |       |  Vol2   |
|        | Fast SSD  |      |    +-----+----+   iscsi/fc/...      +---------+
|        +-----------+      |    | /dev/sdc +-------------+-------+  Vol3   |
|                           |    +----------+             |       +---------+
|                           |                             |       |  Vol4   |
|                     +-----+----+    iscsi/fc/...        |       +---------+
|                     | /dev/sdb +--------------------------------+  Vol5   |
|                     +----------+                        |       +---------+
|                                                         |       |  .....  |
+---------------------------------------------------------+       +---------+

更改包括

  • 在卷类型的额外规格中添加“cacheable”属性

    • 卷本地缓存无法与多重附加一起工作。 因此,在将“cacheable”添加到额外规格时,cinder 应检查是否存在“multiattach”属性。 如果存在“multiattach”,则 cinder 应拒绝将“cacheable”属性添加到卷类型,反之亦然。

    • 在 connection_info 中填充“cacheable”属性。 这样 os-brick 就可以知道一个卷是否可以被缓存。

  • 在 os-brick 中添加一个通用的框架,用于不同的缓存软件。 此框架应灵活,以支持不同的缓存软件。

    1) 将添加一个基类 - CacheManager,主要函数将是

    • __init__()

      此函数将接受来自 Nova 的参数。 参数包括

      root_helper - 用于缓存软件管理工具。

      connection_info - 包含设备路径

      cache_name - 指定缓存软件名称,当前仅支持“opencas”

      instance_ids - 指定可以使用的缓存实例。 os-brick 在这些实例中选择最佳的一个

    • attach_volume()

      此函数将在 Nova 调用(在函数 _connect_volume 中)尝试附加卷时调用,以设置卷的缓存。

    • detach_volume()

      此函数将在 Nova 调用(在函数 _disconnect_volume 中)尝试分离卷时调用,以释放缓存。

    2) 在 __init__.py 中,将添加一个缓存软件及其 python 类的映射。 这样 os-brick 就可以根据缓存名称找到正确的类。

    CACHE_NAME_TO_CLASS_MAP = {

    “opencas”: ‘os_brick.caches.opencas.OpenCASEngine’,…

    }

    同时,将添加一个函数,例如 _get_engine(),来遍历该映射以找到正确的类。

  • 在 os-brick 中添加对 open-cas 的支持。

    实现 open-cas 的 attach_volume/detach_volume 函数。

代码流程如下

            Nova                                        osbrick

                                              +
         +                                    |
         |                                    |
         v                                    |
   attach_volume                              |
         +                                    |
         |                                    |
         +                                    |
       attach_cache                           |
             +                                |
             |                                |
             +                                |
 +-------+ volume_with_cache_property?        |
 |               +                            |
 | No            | Yes                        |
 |               +                            |
 |     +--+Host_with_cache_capability?        |
 |     |         +                            |
 |     | No      | Yes                        |
 |     |         |                            |
 |     |         +-----------------------------> attach_volume
 |     |                                      |        +
 |     |                                      |        |
 |     |                                      |        +
 |     |                                      |      set_cache_via_casadm
 |     |                                      |        +
 |     |                                      |        |
 |     |                                      |        +
 |     |                                      |      return emulated_dev_path
 |     |                                      |        +
 |     |                                      |        |
 |     |         +-------------------------------------+
 |     |         |                            |
 |     |         v                            |
 |     |   replace_device_path                |
 |     |         +                            |
 |     |         |                            |
 v     v         v                            |
                                              |
attach_encryptor and                          |
rest of attach_volume                         +
  • 卷本地缓存位于加密器之上,性能会更好,但会暴露缓存设备中的解密数据。 因此,出于安全考虑,缓存应位于 Nova 实现中的加密器之下。

备选方案

  • 将本地 SSD 分配给特定的 VM。 然后,VM 可以内部使用 bcache 针对临时磁盘来缓存其卷,如果他们想这样做的话。

    缺点可能包括

    • 只能加速一个 VM。 快速 SSD 的功能不能与其他 VM 共享。 与 RAM 不同,SSD 通常在 TB 级别,并且足以缓存一个节点上的所有 VM。

    • VM 的所有者应显式设置缓存。 但并非所有 VM 所有者都想这样做,并且并非所有 VM 所有者都具有执行此操作的知识。 但他们肯定希望卷性能默认情况下更好。

  • 创建一个专用的缓存集群。 将所有缓存(NVME SSD)安装在缓存集群中作为大型缓存池。 然后,将一定量的缓存分配给特定的卷。 分配的缓存可以通过 NVMEoF 协议安装在计算节点上。 然后使用缓存软件执行相同的缓存。

    但这将是本地 PCIe 和远程网络之间的竞争。 这样做的一个缺点是:存储服务器的网络将成为瓶颈。

    • 延迟:存储集群通常通过 iscsi/fc 协议或通过 librbd(如果使用 ceph)提供卷。 延迟将在毫秒级别。 即使使用 TCP 上的 NVME,延迟也将是几百微秒,具体取决于网络拓扑。 相比之下,NVME SSD 的延迟约为 10 微秒,以 Intel Optane SSD p4800x 为例。

  • 可以在后端存储侧添加缓存,例如在 ceph 中。 存储服务器通常具有自己的缓存机制,例如使用内存作为缓存,或使用 NVME SSD 作为缓存。

    与上述解决方案类似,延迟是一个缺点。

REST API 影响

数据模型影响

安全影响

  • 缓存软件将在分离卷时从缓存设备中删除缓存的卷数据。 但通常不会擦除缓存设备中的相关扇区。 因此,在理论上,在被覆盖之前,卷数据仍然在缓存设备中。 除非拔出缓存设备,否则可以接受,因为卷本身也已挂载并可见在主机操作系统上。 具有加密的卷如果没有加密位于卷本地缓存之上,则没有这个问题。

通知影响

其他最终用户影响

性能影响

  • 将减少 VM 卷的延迟

其他部署者影响

  • 需要在计算节点上配置缓存软件

开发人员影响

  • 其他开发人员可以稍后添加对其他缓存软件的支持

实现

负责人

主要负责人

Liang Fang <liang.a.fang@intel.com>

工作项

  • 实现一个通用的框架来支持不同的缓存软件

  • 支持 open-cas

  • 将添加单元测试

依赖项

测试

  • 将实现单元测试、tempest 和其他相关测试。

  • 特别是测试用例:利用 DRAM 模拟快速 ssd,作为 open-cas 的缓存;使用 fio 执行 4k 块大小的随机读取测试;比较有/无缓存的卷的结果。 预期行为是:缓存的卷将获得更低的延迟。

文档影响

  • 需要文档。 用户文档说明如何使用缓存软件来缓存卷。

参考资料

[1] https://review.opendev.org/#/c/663549/ [2] https://review.opendev.org/#/c/663542/ [3] https://review.opendev.org/#/c/700799/