DAOS存储模型与事务模型

存储模型

概括

下图为DAOS存储模型的基本框架

../graph/daos_abstractions.png

一个DAOS pool是跨一组target的存储预留区,每个target上分配给一个pool的实际空间大小称为pool shard。分配给pool的空间总大小在其创建时已经确定,也可以通过扩展pool shard大小或者通过增加更多的target(增加更多pool shard)。pool提供存储虚拟化,是预分配和隔离的单元。DAOS pool不可跨系统扩展。

一个池可以托管多个称为 DAOS 容器的事务对象存储,每个容器都是一个私有对象地址空间,可以被事务性地修改,并且独立于存储在同一池中的其他容器。容器是快照和数据管理的单元。属于容器的 DAOS 对象可以分布在池的任何目标上,以提高性能和弹性,并且可以通过不同的 API 访问,以有效地表示结构化、半结构化和非结构化数据。

DAOS Pool

pool以UUID为唯一标识,并在一个持久版本列表的pool map中维护target的资格,成员资格按序号变化。pool map不仅记录活跃target,还以树的形式记录存储拓扑关系,这可以用来识别共享相同硬件的target。此框架有效地表示分层容错域,然后使用这些域来避免将冗余数据放置在受相关故障影响的目标上。pool map可以添加新的或者删除失效的target。此外,pool map是完全版本化的,这有效地为映射的每次修改分配了一个唯一的序列,特别是对于失败的节点删除。

pool shard是特定target上的持久持久存储区,有着固定大小且容量满会失效。pool shard的数据量可以被查询,其上任何数据了类型的数据占用存储量也可以被查询。

一旦失效target在pool map中清除,pool内部的数据冗余将自动在线恢复。这个自愈过程叫做rebuild。rebuild操作会定期pool持久内存的特殊日志中以解决级联终止。当新target加入,数据会自动迁移到新加入的target中从而在所有成员中平衡空间利用。这一过程称为space rebalancing,使用专用持久化日志保证过程安全。pool中的target跨越多个存储节点,数据和元数据被分布式存储来实现水平扩展性,同时被备份和纠删编码以实现持久性和可用性。(水平扩展还是行式存储,ap性能会不会受影响)

创建pool时需要定义一系列属性,这些属性可以被用户指定并持久存储。pool只对已授权应用可用,其中多种安全框架例如NFSv4访问控制列表以及Kerberos第三方验证等。连接pool时安全机制强制执行,一旦成功连接pool,连接上下文会被返回给应用进程。

pool中存储了不同的元数据,例如pool map、身份验证、授权信息、用户属性、特性和重建日志。这些元数据非常关键,需要最高级别的恢复能力。因此,Pool 的元数据被复制到几个来自不同高级容错域的节点上。对于具有数十万个存储节点的非常大的配置来说,这些节点中只有很小的一部分(大约几十个)运行 Pool 元数据服务。在存储节点数量有限的情况下,DAOS 可以依赖一致性算法来达成一致,在出现故障时保证一致性,避免脑裂。

要访问 Pool,用户进程应该连接到 Pool 并通过安全检查。一旦授权,Pool 就可以与任何或所有对等应用程序进程(类似 openg() POSIX 扩展)共享(通过 local2global()global2local() 操作)连接。这种集体连接机制有助于在数据中心上运行大规模分布式作业时避免元数据请求风暴。当发出连接请求的原始进程与 Pool 断开连接时,Pool 连接将被注销。

DAOS 容器

Container 代表 Pool 中的对象地址空间,由 Container UUID 标识。下图显示了用户(I/O 中间件、特定领域的数据格式、大数据或 AI 框架等)如何使用 Container 来存储相关数据集:

../graph/containers.png

与 Pool 一样,Container 可以存储用户属性。Container 在创建时必须传递一组属性,以配置不同的功能,例如校验和。

要访问 Container,应用程序必须首先连接到 Pool,然后打开 Container。如果应用程序被授权访问 Container,则返回 Container 句柄,它的功能包括授权应用程序中的任何进程访问 Container 及其内容。打开进程可以与所有对等进程共享此句柄。它们的功能在 Container 关闭时被撤销。

Container 中的对象可能具有不同的模式,用于处理数据分布和 Target 上的冗余,定义对象模式所需的一些参数包括动态或静态条带化、复制或纠删码。Object 类定义了一组对象的公共模式属性,每个 Object 类都被分配一个唯一的标识符,并在 Pool 级别与给定的模式相关联。一个新的 Object 类可以在任何时候用一个可配置的模式来定义,这个模式在创建之后是不可变的(或者至少在属于这个类的所有对象都被销毁之前)。

为了方便起见,在创建 Pool 时,默认情况下会预定义几个最常用的 Object 类:

Object Class (RW = read/write, RM = read-mostly Redundancy Layout (SC = stripe count, RC = replica count, PC = parity count, TGT = target
Small size & RW Replication static SCxRC, e.g. 1x4
Small size & RM Erasure code static SC+PC, e.g. 4+2
Large size & RW Replication static SCxRC over max #targets)
Large size & RM Erasure code static SCx(SC+PC) w/ max #TGT)
Unknown size & RW Replication SCxRC, e.g. 1x4 initially and grows
Unknown size & RM Erasure code SC+PC, e.g. 4+2 initially and grows

如下所示,Container 中的每个对象都由一个唯一的 128 位对象地址标识。对象地址的高 32 位保留给 DAOS 来编码内部元数据,比如 Object 类。剩下的 96 位由用户管理,在 Container 中应该是唯一的。只要保证唯一性,栈的上层就可以使用这些位来编码它们的元数据。DAOS API 为每个 Container 提供了 64 位可伸缩对象 ID 分配器。应用程序要存储的对象 ID 是完整的 128 位地址,该地址仅供一次性使用,并且只能与单个对象模式相关联。

1
2
3
4
5
<---------------------------------- 128 bits ---------------------------------->
--------------------------------------------------------------------------------
|DAOS Internal Bits| Unique User Bits |
--------------------------------------------------------------------------------
<---- 32 bits ----><------------------------- 96 bits ------------------------->

Container 是事务和版本控制的基本单元。所有的对象操作都被 DAOS 库隐式地标记为一个称为 epoch 的时间戳。DAOS 事务 API 允许组合多个对象更新到单个原子事务中,并基于 epoch 顺序进行多版本并发控制。所有版本更新都可以定期聚合,以回收重叠写入所占用的空间,并降低元数据复杂性。快照是一个永久引用,可以放置在特定的 epoch 上以防止聚合。Container 元数据(快照列表、打开的句柄、对象类、用户属性、属性和其他)存储在持久性内存中,并由专用 Container 元数据服务维护,该服务使用与父元数据 Pool 服务相同的复制引擎或自己的引擎,这在创建 Container 时是可配置的。

与 Pool 一样,对 Container 的访问由 Container 句柄控制。要获取有效的句柄,应用程序进程必须打开 Container 并通过安全检查。然后,可以通过 Container 的 local2global()global2local() 操作与其他对等应用程序进程共享此句柄。

DAOS对象

为了避免传统存储系统常见的扩展问题和开销,DAOS 有意将对象简化,不提供类型和架构之外的默认对象元数据。这意味着系统不维护时间、大小、所有者、权限,甚至不跟踪开启者。

为了实现高可用性和水平伸缩性,DAOS 提供了许多对象模式(复制/纠删码、静态/动态条带化等)。模式框架是灵活的,并且易于扩展,以允许将来使用新的自定义模式类型。模式布局是在对象标识符和 Pool 映射打开的对象上通过算法生成的。通过在网络传输和存储期间使用校验和保护对象数据,确保了端到端的完整性。

可以通过不同的 API 访问 DAOS 对象:

  • Multi-level key-array API 是具有局部性特征的本机对象接口。key 分为 distribution key (dkey) 和 attribute key (akey)。dkey 和 akey 都可以是可变长度的类型(字符串、整数或其它复杂的数据结构)。同一 dkey 下的所有条目都保证在同一 Target 上并置。与 akey 关联的值可以是不能部分修改的单个可变长度值,也可以是固定长度值的数组。akeys 和 dkey 都支持枚举。
  • Key-value API 提供了一个简单的键和可变长度值接口。它支持传统的 put、get、remove 和 list 操作。
  • Array API 实现了一个由固定大小的元素组成的一维数组,该数组的寻址方式是 64 位偏移寻址。DAOS 数组支持任意范围的 read、write 和 punch 操作。

事务模型

DAOS API 支持分布式事务,允许将针对属于同一 Container 的对象的任何更新操作组合到单个 ACID 事务中。分布式一致性是通过基于多版本时间戳排序的无锁乐观并发控制机制提供的。DAOS 事务是可串行化的,可以在特定的基础上获取部分需要的数据集。

DAOS 版本控制机制允许创建持久的 Container 快照,该快照提供 Container 的实时分布一致性视图,该视图可用于构建生产者-消费者管道。

Epoch 和时间戳

每个 DAOS I/O 操作都有一个称为 epoch 的时间戳。epoch 是一个 64 位整数,它集成了逻辑和物理时钟(详见 HLC paper)。DAOS API 提供了辅助函数,用于将 epoch 转换为传统的 POSIX 时间(即 struct timespec,详见 clock_gettime(3))。

Container快照

如下图所示,Container 的内容可以随时快照。

../graph/container_snapshots.png

DAOS 快照非常轻量级,并且使用与创建快照的时间相关联的 epoch 进行标记。一旦创建成功,快照将一直保持可读性,直到它被显式销毁。在特定快照未被销毁前,Container 的内容可以回滚到该快照。Container 快照功能支持本机生产者/消费者管道:

../graph/producer_consumer.png

一旦成功写入数据集的一致版本,生产者 (Producer) 将生成一个快照。使用者 (Consumer) 的应用程序可以订阅 Container 快照事件,以便在生产者提交更新时可以处理新的更新。快照的不变性保证了使用者可以看到一致的数据,即使生产者继续进行新的更新。生产者和消费者实际上都在 Container 的不同版本上操作,不需要任何串行化操作。一旦生产者生成了数据集的新版本,使用者就可以查询两个快照之间的差异,并且只处理增量修改。

相关内容:

安装配置:https://docs.daos.io/v2.2/QSG/setup_rhel/

管理:https://docs.daos.io/v2.2/admin/hardware/

测试与Benchmarking:https://docs.daos.io/v2.2/testing/autotest/

华为云社区:https://bbs.huaweicloud.com/blogs/253715