ZNS 架构实现 : 解决传统SSD问题的高性能存储栈设计

news2024/11/9 9:28:27

声明

主页:元存储的博客_CSDN博客

依公开知识及经验整理,如有误请留言。

个人辛苦整理,付费内容,禁止转载


内容摘要

2.2 ZNS 的架构实现

先看看 支持zone 存储的 SMR HDD 以及 支持 zonefs 的 nvme ssd 的整个存储栈形态

在这里插入图片描述

 

其中对于ceph 这样的应用来说 bluestore或者Seastore 这样的后端引擎是直接管理裸设备的,所以不需要文件系统支持。当然如果需要,也可以通过一个内核支持的小型文件系统zonefs 来进行数据访问。

但是这个小型文件系统过去简单,它将每一个zone空间暴露为一个文件,使用LBA0 来存储superblock,并没有复杂的inode/dentry 这种元数据的管理机制,在这个文件系统上创建/删除/重命名都是不允许的。针对数据的写还是类似zone storage的要求,即通过一个WP来进行写,如果这个zone 空间对应的文件被写满了,则WP 无法写入直到 对这个zone 执行了reset ,才会将WP 重新移动到LBA0,从而可写。

它对于Rocksdb 来说功能还不足,而且Rocksdb 只需要一个文件 用户态的 backend,虽然如是说,但是如何在rocksdb调度写的时候分配一个最优的zone,如何选择合适的时机删除sst文件(重置zone空间)都需要精心的设计在里面。

后来,Hans 主导设计了 Zenfs 来作为Rocksdb 的Backend 来进行端到端的请求调度,且选择最优的数据存储方式 并且在降低写放大(LSM-tree)、SSD 的磨损均衡、降低读长尾 等都做了较多的探索。

大体架构如下:

在这里插入图片描述

 

接下来我们仔细看看 ZNS 内部的一些实现的特性 以及 Zenfs 的详细设计实现。

2.2.1 ZNS 实现过程中的一些PR

ZNS的特性 需要内核支持,所以开发了ZBD(zoned block device) 内核子系统 来提供通用的块层访问接口。除了支持内核通过ZBD 访问ZNS之外,还提供了用户API ioctl进行一些基础数据的访问,包括:当前环境 zone 设备的枚举,展示已有的zone的信息 ,管理某一个具体的zone(比如reset)。

在 FIO 内部支持了 对 ZBD的压测。

在近期,为了更友好得评估ZNS-ssd的性能,在ZBD 上支持了暴露 per zone capacity 和 active zones limit。

Zenfs 的设计 并 作为 rocksdb 的一个文件系统backend。

这里是对应修改代码行数的概览:

在这里插入图片描述

 

从代码行数上来看,可以说是非常得轻量了。

2.2.2 Zenfs 的设计实现

2.2.2.1 为什么zenfs如此看重rocksdb(LSM-tree 架构)

之所以ZNS 社区对Rocksdb 这么看重,代码行数上的贡献上可以说 在Rocksdb 上投入的精力远超其他方面。

从LSM-tree原理上,我们可以看到几点:

LSM-tree 的写入是append 顺序写,这适配 ZNS 的 zone 架构来说简直再合适不过。
LSM-tree 的compaction 也是顺序写一批数据,然后再集中删除,这也符合 ZNS 的空间回收方式(每一个zone 状态是Full的时候,想要重新写,只有reset了)。在好的配置下相当于 SSD内部的GC 和 rocksdb的compaction 完美结合了。
LSM-tree on 传统 ssd 的痛点比较明显。读方面:LSM-tree分层软件架构对读性能不友好(长尾较为严重),再加上ssd 的FTL GC会间接 让长尾不可预估;引以为傲的顺序写优势也因为SSD 内部的FTL 频繁GC 导致写性能抖动且相比于空载时的下降。这一些痛点在ZNS 下都能够被很好的避免甚至完全解决。
抛开 LSM-tree 本身on ssd 的劣势 之外,Rocksdb 则有一些自身特有的优势,值得 ZNS 社区持续投入:

k/v 存储领域里应用广泛,适合用于高速存储介质(NVMe-ssd)
开源 且 拥有活跃的社区,社区也在持续跟进新的存储技术。包括:io_uring / spdk 等
可插拔的存储后端设计,实现一个fs backend,移植就非常容易(将zenfs 编译到rocksdb 代码中就可以看出来)。
2.2.2.2 zenfs 详细设计

先看一下总体Zenfs的系统架构概览,这个图是论文中的图,更简洁一些:

在这里插入图片描述

因为它要作为Rocksdb 的fs backend,负责和zoned block devcei 进行交互,那其继承自FileSystemWrapper类的基本接口肯定是都实现了。

主要的组件如下几个:

Journaling and Data.

Zenfs 定义了两种类型的zones: journal 和 data. 代码中ZonedBlockDevice 类管理的也就是两个vector , meta_zones和io_zones,下文统一称为Journal Zones 和 Data Zones。

其中 Journal Zones 用来管理文件系统的元数据,包括异常时恢复文件系统的一致性状态,维护文件系统的superblock 以及 映射 wal 和 数据文件到 zone中。

Data zones 则主要用于保存 sst 这样的数据文件。

Extents.

Rocksdb 的数据文件会被映射 写入到一个extents 集合中 std::vector<ZoneExtent*> extents_。其中一个extent 时一个变长但block对齐的连续LBA地址,而且会拿着一个标识当前sst的信息 顺序写入到一段zone空间中,会用ZoneFile这个数据结构标识文件以及属于这个文件的extents 数组。每一个 zone空间能够存储多个extents,但是一个extent 不会跨越多个zone而存在。

Extent 的分配和释放都是一个内存数据结构来管理的,当一个文件变比或者这个extent的数据要持久化到磁盘 调用Fsync/Append时,内存的数据结构也会对应持久化到journal_zone之中。而且内存中这个数据结构会持续跟踪extents的分配情况,当一个zone内的所有extents 所属的文件都被删除,这个zone就可以被reset了,方便后续的 reuse。

Superblock.

Superblock 主要用来初始化Zenfs 或者 从磁盘异常恢复Zenfs 的状态。Superblock 会通过unique id, 魔数和用户选项 来标识属于当前磁盘的Zenfs。这个唯一标识 是 UUID(unique identifier),允许用户识别对应磁盘上的文件系统,即当磁盘的盘符重启或者外部插拔发生变化的时候仍然能够识别到这上面的文件系统。

Journal.

Journal的主要工作是维护 superblock 和 WAL 以及 sst 和 存储于zone中的extents的映射。
Journal的数据主要存储在上图中的 Journal Zones中,也就是 代码中的 meat_zones,而且journal zone 是位于整个存储设备上的前三个永远不会offline的 zone ZENFS_META_ZONES。其中任何时刻,总会有一个zone是处于active的, 也就是必须可写的,不然这两个zone 被closed 的话就无法跟踪元数据了的更新了。

其中最开始的那个active zone 会有一个header,包含:sequence number(每当有一个journal zone被初始化的时候都会自增),superblock 数据结构,当前journal 状态的一个snapshot。初始化的时候,header被持久化完成,整个zone剩下的capacity就可以开始接受新的data 数据更新了。

我们从一个 ZNS 磁盘初始化一个Zenfs的过程需要执行:

.plugin/zenfs/util/zenfs mkfs --zbd=$DEV --aux_path=/tmp/$AUXPATH --finish_threshold=10 --force

注意:在 $DEV 的设备名称只能是 nvme0n1 或者 nvme1n这种,不能加 /dev/nvme0n1,zenfs 会自己去环境中找 nvme0n1,不需要用户指定路径。
1 Zenfs::MkFS 所有的meta zone都会reset,并且在第一个meta zone上创建一个zenfs文件系统,执行如下内容

写一个superblock 的数据结构,包括sequence 的初始化 并持久化
初始化一个空的snapshot,并持久化。
2 Zenfs::Mount 从磁盘 Recovery 一个已经存在的zenfs 的几个步骤如下:

现在是三个journal zones,最开始的时候需要先读取三个 journal zones 的第一个LBA内容,从而确定每一个zone 的sequence,其中seq 最大的是当前的active zone(拥有最全的元数据新的zone)。
读取active zone的header 内容,并且初始化 superblock 和 jourace state。
对 journal 的更新都会同步到到 header 的snapshot 中。
这两步操作基本就构建好了一个完整的Zenfs 状态,后续就会持续接受用户的写入。

写入过程中 sst 的数据存储是通过 保存着extent 并由extent持久化到对应的zone空间中,那 如何选择一个Data zone 来作为存储当前文件数据的呢? 因为不同的zone 在实际接受数据存储时其 capacity 的容量是变化的。如果一个sst 文件的存储是跨zone的,那最后对一个zone的 reset 还需要考虑这个文件 是否被删除。

Best-Effort Alogthrim for Zone Selection

Zenfs 这里开发了 Best-effort 算法来选择zone 作为 rocksdb sst 文件的存储。Rocksdb 通过对 WAL 和 不同 level 的 sst 设置不同的 write_hint 来表示这一些文件的生命周期。

选择哪一种 write_hint, 则通过如下逻辑进行:

Env::WriteLifeTimeHint ColumnFamilyData::CalculateSSTWriteHint(int level) {
  if (initial_cf_options_.compaction_style != kCompactionStyleLevel) {
    return Env::WLTH_NOT_SET;
  }
  if (level == 0) {
    return Env::WLTH_MEDIUM;
  }
  int base_level = current_->storage_info()->base_level();

  // L1: medium, L2: long, ...
  if (level - base_level >= 2) {
    return Env::WLTH_EXTREME;
  } else if (level < base_level) {
    // There is no restriction which prevents level passed in to be smaller
    // than base_level.
    return Env::WLTH_MEDIUM;
  }
  return static_cast<Env::WriteLifeTimeHint>(level - base_level +
                            static_cast<int>(Env::WLTH_MEDIUM));
}


也就是从当前总层数开始,倒数两层的sst 文件拥有最长的生命周期,level0 拥有 WITH_MEDIUM 的生命周期,WAL 则拥有最短的生命周期WITH_SHORT。

回到Zenfs 选择zone 的过程,总的来说就是让 life_time 小的文件尽量存放在和它 life_time接近的zone中,这样更大概率得统一对整个zone 进行 reset :

(1) 对于新的写入,直接分配一个新的zone

(2) 优先从 active zones 中进行分配,如果能够找到合适的zone,则直接Reset 这个zone,并作为当前文件的存储。合适的zone 的条件是:如果当前文件的lifetime 比 active zone 中最老的数据 还小,则当前zone 比较合适作为当前文件的存储;如果有多个active zone满足这个条件,则选择一个最近比较的active zone。

(3) 如果从active zone中没有找到合适的zone,那直接分配一个新的zone。当然,分配的过程也就意味着判断active zone个数有没有超过 max_nr_active_io_zones_ ,超过了则需要关闭一个 active zone,然后才能分配一个新的zone。

逻辑如下:

Zone *ZonedBlockDevice::AllocateZone(Env::WriteLifeTimeHint file_lifetime) {
  ...
  // best effort 算法的逻辑
  for (const auto z : io_zones) {
    if ((!z->open_for_write_) && (z->used_capacity_ > 0) && !z->IsFull()) {
      // 主要就是拿着当前 zone 的lifetime 和当前文件的file_lifetime (也就是write_hint)进行对比
      // 如果文件的life_time小,则当前zone 满足存储需求。
      unsigned int diff = GetLifeTimeDiff(z->lifetime_, file_lifetime);
      if (diff <= best_diff) {
        allocated_zone = z;
        best_diff = diff;
      }
    }
  }
  ...
  // 如果从没有为当前文件找到找到合适的zone,那就得分配一个新的了
  if (best_diff >= LIFETIME_DIFF_NOT_GOOD) {
    /* If we at the active io zone limit, finish an open zone(if available) with
     * least capacity left */
    if (active_io_zones_.load() == max_nr_active_io_zones_ &&
        finish_victim != nullptr) {
      s = finish_victim->Finish();
      if (!s.ok()) {
        Debug(logger_, "Failed finishing zone");
      }
      active_io_zones_--;
    }

    if (active_io_zones_.load() < max_nr_active_io_zones_) {
      for (const auto z : io_zones) {
        if ((!z->open_for_write_) && z->IsEmpty()) {
          z->lifetime_ = file_lifetime;
          allocated_zone = z;
          active_io_zones_++;
          new_zone = 1;
          break;
        }
      }
    }
  }
  ...
}


AllocateZone 完成之后就可以 更新当前文件在 分配的zone 中的extent(主要存放偏移地址和length),通过IOStatus Zone::Append 进行文件数据的实际写入了。

总的来说,Zenfs 通过 Best-effort 算法,根据 Rocksdb 配置的write_hint_存储 data文件和zone 接近的生命周期 来加速过期zone的回收,极大得减少了 ZNS 的空间放大问题,根据论文中的数据,说能够保持空间放大在10% 左右(可以说是整个LSM-tree + SSD 的空间放大,数据没问题的话已经很了不起了)。

当然,想要有这样的测试数据,需要对rocksdb 的参数配置进行调整,可以通过执行 Zenfs下的一个脚本来达到这个目的: ./zenfs/tests/get_good_db_bench_params_for_zenfs.sh nvme2n1 可以获取到官方推荐的一个配置,建议让 target_file_size 和 zone 配置的大小对齐。

Zenfs 也有active_zone_limits 的限制,即我们在AllocateZone 函数中可以看到,分配一个新的zone 的话如果当前active zone 的个数达到了max_nr_active_io_zones_ ,需要先关闭之前的一个zone才行,也就是在 Rocksdb 中也会有 active zone个数的限制。当然这方面 Zenfs 也做了对应的测试,发现 active zone 的个数小于6的话 会对写性能有影响, 但是达到12的话后面再增加对写性能没有太大的影响。

————————————————
版权声明:本文为CSDN博主「z_stand」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Z_Stand/article/details/120933188


参考

免责声明

本文根据公开信息整理,旨在介绍更多的存储知识,所载文章仅为作者观点,不构成投资或商用建议。本文仅用于学习交流, 不允许商用。若有疑问或有侵权行为请联系作者处理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/420668.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

前端项目-12-个人中心-二级路由配置-导航守卫-懒加载

目录 1-个人中心 1.1-个人中心路由注册 1.2-拆分二级路由组件 1.3-动态渲染我的订单页面 2-导航守卫优化 2.1-用户未登录导航守卫优化 2.2-路由独享 2.3-组件内守卫 3-懒加载 3.1-图片懒加载 3.2-路由懒加载 4-map文件处理 1-个人中心 需求&#xff1a;当用户点击支…

计算机图形学 | 实验五:模型导入

计算机图形学 | 实验五&#xff1a;模型导入计算机图形学 | 实验五&#xff1a;模型导入模型加载库AssimpAssimp简介Assimp构建Mesh && Model 类的创建MeshModel绘制模型华中科技大学《计算机图形学》课程 MOOC地址&#xff1a;计算机图形学&#xff08;HUST&#xff…

进阶C语言

1.数据的存储 1.1 为什么数据在内存中存放的是补码 因为CPU只有加法器,而使用补码&#xff0c;就可以将符号位和数值域统一处理(即统一处理加法和减法)且不会需要额外的硬件电路。 1.2 为什么会有大小端 这是因为在计算机系统中,是以字节为单位的,比如: 每个地址单元都对应着…

双指针算法初阶

前言&#xff1a;首先&#xff0c;这是不是你所了解的双指针算法&#xff1f; for (int i 0; i < n; i) {for (int j 0; j < n; j){...} } 那你就要继续往下看了&#xff0c;双指针算法可不是简单的两层的for循环暴力&#xff0c;这并不能起到时间优化的作用。 那话…

基于html+css的图片展示9

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

educoder实训——数值类型

第1关:三角形周长及面积 任务描述 输入的三角形的三条边a、b、c 的长度,计算并依次输出三角形的周长和面积,结果严格保留2位小数。测试用例的数据保证三角形三边数据可以构成三角形。 三角形面积计算公式: 其中s=(a+b+c)/2。 输入格式 分三行输入 3 个浮点数,表示三…

银行数字化转型导师坚鹏:金融数字化转型助力乡村振兴及案例

金融数字化转型助力乡村振兴及案例课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚如何借助数字化转型助力乡村振兴&#xff1f; 不知道普惠金融模式和产品如何有效创新&#xff1f; 不知道数字化转型助力乡村振兴的成功案例&#xff1f; 课程特色&#xff1…

【用AI写周报,“卷死”同事】打造一款自动生成周报的微信小程序

文章目录前言步骤1&#xff1a;创建一个ChatGPT账号步骤2&#xff1a;创建一个微信小程序并配置API。步骤3&#xff1a;在微信开发者工具中创建一个新的微信小程序项目步骤4&#xff1a;创建ChatGPT API云函数步骤5&#xff1a;创建UI界面步骤6&#xff1a;创建发送邮件的云函数…

Kubernetes 1.27 正式发布

Kubernetes 1.27 正式发布&#xff0c;这是 2023 年的第一个版本。这个版本包括 60 项增强功能。其中 18 项增强功能进入 Alpha、29 项进入 Beta&#xff0c;还有 13 项升级为 Stable 稳定版。 主题和标识 Kubernetes v1.27 的主题是 Chill Vibes 新内容 冻结 k8s.gcr.io镜像…

replugin宿主与插件通信小结

近来replugin开发中遇到宿主和插件间需要通信的情形&#xff0c;思来只有进程间通信(IPC)才是比较好的宿主与插件的通信方式。而Android进程间通信主要有2种方式&#xff1a;Messenger和AIDL。 AIDL&#xff08;Android Interface Definition Language&#xff09;是Android接…

矩阵和线性代数的应用

矩阵和线性代数是数学中重要的概念&#xff0c;它们被广泛应用于物理、工程、计算机科学、经济学等众多领域。本文将讨论矩阵和线性代数的一些基本概念以及它们在实际应用中的重要性和影响。 一、矩阵和线性代数的基本概念 矩阵是由数字组成的矩形数组。它可以表示线性方程组…

线程池并发服务器

线程池技术 线程池技术是一种典型的生产者-消费者模型。 线程池技术是指能够保证所创建的任一线程都处于繁忙状态&#xff0c;而不需要频繁地为了某一任务而创建和销毁线程&#xff0c;因为系统在创建和销毁线程时所耗费的cpu资源很大。如果任务很多&#xff0c;频率很高&am…

Android中级——系统信息与安全机制

系统信息与安全机制系统信息获取/system/build.prop/procandroid.os.buildSystemPropertyPackageManagerActivityManagerpackages.xmlpermissions标签package标签perms标签安全机制Apk反编译apktooldex2jarjd-guiApk加密系统信息获取 /system/build.prop 存放一些配置信息&am…

Seaborn 变量分布分析

文章目录一、数据集1.1 下载数据集1.2 字段含义说明1.3 导入数据集二、初步分析2.1 缺失值分布查看2.2 异常值分布查看2.3 查看变量分布三、数值变量分析3.1 replot()&#xff1a;多个变量之间的关联关系3.2 lmplot()/regplot&#xff1a;分析两个变量的线性关系3.3 displot()&…

从前序与中序遍历序列构造二叉树——力扣105

题目描述 法一&#xff09;递归 复杂度分析 代码如下 class Solution { private:unordered_map<int, int> index;public:TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_ri…

Qt Quick - StackView

StackView 使用总结一、概述二、在应用中使用StackView三、基本的导航1. push Item2. pop Item3. replace Item四、深度链接五、寻找Item六、转换六、Item的所有权七、大小一、概述 StackView可以与一组相互链接的信息页面一起使用。例如&#xff0c;电子邮件应用程序具有单独…

HTML5 <img> 标签

HTML5 <img> 标签 实例 HTML5 <img>标签用于向网页中添加相关图片。 如何插入图像&#xff1a; <img src"smiley-2.gif" alt"Smiley face" width"42" height"42">尝试一下 &#xff08;更多实例见页面底部&…

基于营销类系统运营活动增长带来的数据库设计演进

一、前言 为了支持业务数据的不断增长&#xff0c;在数据库层面的性能提升主要体现在几个维度&#xff1a;1&#xff09;数据降级&#xff1b;2&#xff09;数据主题分而治之&#xff1b;3&#xff09;实时交易转异步&#xff1b;4&#xff09;硬件扩容&#xff0c;当然网上一…

question

4、Mysql高可用有几种方案&#xff0c;分别有什么特点? 特点优点缺点mysql group replication(MGR)组复制组内一半节点同意即可提交更改操作、最多支持 9 个节点、基于MRG插件、多节点写入支持、故障自动检测、引擎必须为 innodb、必须有主键、binlog 为 row强一致、paxos协议…

Arcgis Engine之打开MXD文档

Arcgis Engine之打开MXD文档概述方法一&#xff1a;方法二&#xff1a;概述 图层加载功能将用到MapControl 控件提供的LoadMxFile 方法。 该方法通过指定的*. Mxd文档路径直接获取 该方法第一个参数是文件路径&#xff0c; 第二个参数是MExd文档中地图的名称或索引&#xff0…