本文整理自 2023 年 7 月 DataFunSummit 2023 数据基础架构峰会——大规模存储架构分论坛的同名主题分享。今天分享的主题是「百度沧海·存储」如何打造千亿文件量级的大规模分布式文件系统。分享内容主要分为三个部分:
-
文件系统的基本概念;
-
元数据服务建模和分析;
-
CFS 元数据服务架构。
1 文件系统的基本概念
1.1 什么是文件系统
文件存储是大家在日常生活中经常遇到的一种存储形式,存在于每一台手机和电脑上。当我们打开电脑的文件浏览器,浏览目录查找、操作文件的时候,其实就是在操作文件系统。和其他存储形式相比,文件系统的最大特点是有一个层级目录树结构。这个结构长什么样子呢?下图是一个例子,我们可以将这个图翻转一下,看起来就像一颗不断生长的树。树中的每一个非叶子结点,都是一个目录,在目录下可以放更多的目录或文件。除了根目录,每个目录或文件都属于一个父目录。
文件系统领域的权威标准是 POSIX。POSIX 是一个接口标准,规定了文件系统需要实现的接口的参数和行为,但是没有做实现上的约束。因此,在实现上不同的文件系统采用的方法和理论是千差万别的。这套标准存在一个不足:标准诞生的年代还没有出现大规模的分布式文件系统,因此对分布式文件系统面临的一些核心问题在标准中没有做出约束和规定。其中一个是多机一致性问题,当一个文件由多个客户端同时操作的时候,不同客户端看到的属性(权限、修改时间等)和数据是不是完全一致的,如果不完全一致这个不一致程度可以放松到什么程度。考虑到性能和实现难度,现实中没有一个文件系统能够做到 100% 兼容 POSIX 标准。从 POSIX 标准的兼容性上看,大致排序是:HDFS 会比网络文件系统和分布式文件系统差一些,做的最好的是本地文件系统。HDFS 对于大家来说是比较熟悉的,在大数据领域有广泛的应用。HDFS 在 POSIX 语义上做了非常多的裁剪和修改,与原始的 POSIX 相比已经有了很大的变化,我们甚至可以将它看成一套独立于 POSIX 的文件系统标准。在这次的分享中,我们不用关注其中的差异,分析的过程和解决方案对包括 HDFS 在内的所有分布式文件系统都是适用的。
1.2 POSIX 标准对文件系统的一些规定
POSIX 规定的接口可以分为三类。第一类是文件的操作,包括对文件的打开、关闭、读写等。第二类是对文件系统目录树本身做的操作,例如创建目录、删除目录、获取目录下所有子项等。第三类是对文件和目录树都适用的,例如获取属性的部分、修改属主、权限等,称为属性操作。一般我们把对目录树的操作和对属性的操作都统称为文件系统的元数据操作。
在 POSIX 中对接口的行为有很多细致的规定。这里列举了一些实现过程中大家比较关注的点。因为时间原因,无法一一展开,挑两个点简单介绍下。
一个是跟 close 相关的点。在原始的 POSIX 标准里,没有要求文件在 close 时候完成数据的持久化,也就是数据不强制落盘。这在分布式系统中会产生问题。我们来看一个典型的场景,业务的一个客户端写数据,其他客户端希望写数据的客户端完成写入后立即能读到数据。如果按照 POSIX 标准来实现的话,写数据的客户端写完文件 close 后,另一个客户端可能看到数据,也可能看不到数据,什么时候能看到取决于写客户端完成数据下刷的时机。这样的情况对很多业务来说是不可接受的。业界常用的做法是支持一种叫 close-to-open(CTO)的一致性要求,它要求写客户端在完成文件的 close 前完成数据的下刷,以保证其他客户端再次 open 文件能够看到完整、正确的数据。
另一个有意思的点和遍历目录有关系。我们平时在电脑上使用 ls 查看一个目录下有哪些文件和子目录,这个操作的底层实现就是遍历目录。对于这个操作,POSIX 标准没有要求返回的子项是按照字母序排序好的。很多小伙伴可能比较困惑,因为实际上看到 ls 展示的是按照字母序排序的结果。这其实是 ls 内部做了排序,ls 有一个参数,加上后可以看到文件系统输出的原始顺序。
1.3 文件系统的发展历史
文件系统的发展经历了三个阶段。
- 第一个阶段是单机时代。在这个阶段,文件系统几乎与 UNIX 操作系统同步产生。那个时候计算机只有单机形态,诞生的文件系统都是单机的。EXT4,XFS 是这类单机文件系统的代表。
- 第二个阶段是专用硬件时代。单机文件系统存在两个明显的不足。一个不足是单机的存储容量有限,另一个不足是单机面临数据可靠性的问题。当单机出现故障时,数据可能丢失,这在很多商业应用中是不可接受的。因此业界发展出第二个阶段的文件系统。这个阶段的特点是将磁盘从单机剥离出来做成带外的存储,例如磁盘阵列。在这样一种形态之上,再去构建文件系统的解决方案,大致可以归为四类。
- 第一类方案还是单机的形态,但是磁盘不在计算机本地,而是在远程,通过 iSCSI/NVMeoF 等网络协议去连接磁盘。这种形态下的文件存储和单机文件系统是没有差别的,主要保证了机器故障后数据还能马上被其他机器挂载访问。
- 第二类方案主要是解决小规模场景下数据共享的问题。这类方案让同一组磁盘可以同时被不同的机器挂载,再运行 OCFS2 等多机共享文件系统,由这些文件系统做一致性的保障,即使不同客户端同时操作,也能保证数据的正确性。由于完全依赖文件系统的节点自己仲裁,这类方案的规模很难扩展到很大。
在上述两种形态之外,企业和学术界注意到一个事实——大家在使用时需要的不是一组磁盘,而是文件系统的服务。于是诞生出直接提供文件系统服务的第三类和第四类方案,分别是 NAS 和并行文件系统。
- 第三类方案 NAS 提供的是一种网络文件系统,以业界通用标准协议(如NFS/SMB 协议)的方式提供访问入口。这类系统的好处在于因为提供的协议是标准的,所以企业在更换供应商时适配成本较低。
- 第四类叫并行文件系统,专门针对 HPC 或 AI 这类有较高性能诉求的场景。NAS 受益于标准协议,也因为标准协议屏蔽了实现细节,导致没有办法很好发挥出文件系统和硬件本身的性能。并行文件系统则更优先考虑性能,提供私有协议的客户端,在软件栈上做了大量的优化,尽可能榨干硬件的性能。
1.4 现代分布式文件系统的基本组成
现代文件系统大致分为三个部分:客户端、元数据服务、数据服务。
客户端提供了业务访问文件系统的入口。在实现方式上,SDK 是 HDFS 主要的使用方式,NFS、SMB、私有协议是 POSIX 文件系统的提供方式。还有一些探索性的项目会使用系统调用拦截的方式,直接 hook 掉 libc 基础库中 open,read,write 等文件系统操作,旁路到文件系统的动态链接库中去执行。除实现方式外,怎样在客户端之间实现数据的一致性是一个比较关键的问题,时间问题这里不做展开。
元数据服务负责维护文件系统的层级结构和属性信息。根据业务的实际需求,元数据服务实现上会支持 POSIX 或 HDFS 协议。对于元数据而言,大家比较关注两个扩展性指标,分别是规模扩展性和性能扩展性。规模扩展性是说数据量在不断变大过程中,文件系统能够满足业务对规模的需求。性能扩展性是说系统规模在不断扩大过程中,系统性能能否随之扩展符合增长预期。
数据服务主要负责文件数据的存储和相关请求的处理。系统层面大家比较关注的一个技术问题是数据的布局——一个或多个文件的数据在文件系统中如何合理分布,以达到更好的均衡性和性能。具体到每个节点,大家还会关心单机引擎是怎么实现的,是对顺序读写比较友好的 append only 的方式,还是为了更好支持随机写,采取 inplace 的方式。此外,大家还会关注数据的冗余方式,通常会在成本和可靠性之间做取舍,是为了更低的成本采取 EC 的方式,还是为了更好的性能采取副本的方式。
1.5 元数据服务是影响文件系统规模的关键组件
元数据服务位于整个服务的关键路径上。在进行所有数据操作前,都要找到对应的目录或文件,这时首先进行的就是元数据的操作。根据我们线上和业界的观察,在很多业务访问模式中,元数据操作的占比是很高的,特别地,系统的文件越小,元数据操作的占比越高。
但是,和数据服务相比,元数据服务的扩展性更弱。不同的文件是天然独立的实体,做数据分布时,不会在数据之间产生依赖关系,容易打散到不同的节点上。对文件不同部分,可以进行进一步切成小分片打散。因此,数据服务更容易扩展到更多的节点上。但是元数据服务的层级结构,会带来不同目录和文件之间的父子依赖关系,使它们不再是无关联的实体,阻碍了服务进行线性扩展。
因此,元数据服务是影响文件系统规模的关键组件,要打造超大规模文件系统必须先做好元数据服务。
2 元数据服务建模和分析
2.1 元数据服务的抽象模型
在进一步分析之前,我们对元数据服务做一个简单的抽象,让大家对这个服务的实现要求有一个直观的认识。元数据服务提供的操作可以分为两大类:读操作和写操作。
所有写操作都需要实现关联变更。关联变更是指在一个目录下创建或删除文件、子目录的时候,需要同步更改父目录属性,整个操作原子完成。这点比较重要是因为涉及到文件系统的一致性。举个简单的例子,更新父目录属性时,其中一个变更是父目录修改时间,这个修改时间很重要。为什么?当客户端浏览一个目录的时候,如果不能感知到修改时间的变化,只能有两种方法来访问:一种是每次访问都要去后端实时拉取最新数据,这会对系统造成非常大的处理压力;另一种方法是不实时拉取,但是需要忍受一段时间数据的错误或滞后,因为没有办法判断数据是否是最新的。有了修改时间之后,客户端如果在较短时间前已经访问过,缓存了一些结果,重新访问时,就有第三种选择,就是和后端目录做一下比对,如果修改时间没有变化,缓存结果就是有效的,可以直接使用。很显然,如果没有关联变更提供原子、同步更新修改时间的保证,第三种选择根本不可能存在。因此,关联变更对于提升系统性能,保障数据一致性是非常关键的。
rename 是写操作中最复杂、最难优化的一个。其他操作只会涉及一个父目录,但rename 可能会涉及到源和目的两个父目录。它还会对路径成环、空目录空文件的处理有很多额外的规定。具体要求可以参考 POSIX 规范,在这里不再展开。
读操作在文件系统中分为点读和范围读两种。点读包括路径查找(lookup)和获取属性(getattr)。在操作一个文件或目录前,文件系统会先通过路径查找,一级一级找到要操作的文件或目录。每一级查找都是一次 lookup,查找结果是在文件系统中唯一代表一个文件或目录的编号,这个唯一编号叫 inode,大部分系统里是一个 64 位的数字。通过 inode 可以进一步查询文件或目录的属性。范围读的主要应用则是目录遍历,用于获取一个目录下所有的文件和目录的列表。
2.2 衡量元数据服务实现优劣的指标项衡量一个元数据服务实现的好坏,有以下指标。
第一个是扩展性。扩展性衡量文件系统可以扩展到多大规模,可以进一步细分为两个指标:第一个是规模扩展性指标,是指文件系统可以存储多少目录项,数亿、百亿、还是千亿。因为系统中文件的占比是最高的,所以这个指标也成为文件系统所能支持的文件数规模。第二个是性能扩展性,是指增加节点时候,整个系统元数据读写 QPS 是否能够根据规模的增长,做到近似线性关系,以及规模增长到哪个量级会达到极限。
第二个是延时,很多对性能敏感的应用会关心单个请求,例如一个创建、读请求需要花多长时间才能完成。
第三个是均衡性。均衡性是指元数据服务是否有能力把系统中的热点进行打散,让不同节点的处理压力大致均衡。如果不能很好做到这一点,上面提到的扩展性也无法做到很高的,因为根据木桶原理,整个系统的性能是由最短的板来决定的,处理能力最差的那个节点限制了系统实际可达到的规模。
2.3 元数据服务架构发展历史业界出现过的元数据架构大致分为三个发展阶段。
第一阶段以 HDFS、GFS 为代表,在整个系统中只有逻辑上单点的元数据服务,显然是没有任何扩展性和均衡性可言的。这类架构的优点也是单点架构,由于不涉及很复杂的多点协作,所以负载相对比较小的情况下,延时表现非常好,随着负载变大,延时也会逐渐增长,当接近系统极限的时候,延时退化比较严重。
到了第二阶段,大家发现单点元数据服务无法满足系统要求,就自然想到扩展到多点,于是出现了耦合式分布式架构。这一代架构是在前一代基础上,通过哈希或者子树的方式,把文件系统不同部分放到不同的节点上,代表系统有 CephFS,HDFS Federation。这类系统规模可以做到百亿级别,读的性能非常好,写的性能和操作有没有跨节点有关。这类系统最大的缺点是文件或目录创建的时候就确定了位置,后续不能在线迁移。这就导致一旦节点上出现多个热点目录无法通过负载均衡的手段疏散。这是限制系统规模只能到百亿级别的一个主要原因。那为什么很难在线迁移呢?这类系统在每个节点上负责了两个事情,一个是数据的存储,一个是文件系统接口内部的计算逻辑。当计算逻辑和存储耦合在一起时候,要在数据搬迁的过程中保证服务的连续性,就必须处理好计算过程中涉及到的状态的保存和恢复,这远比搬迁无状态的数据复杂非常多,工程难度极高。这类系统的均衡问题一直是业界研究的热点,但基本都停留在理论层面上,鲜有在生产实践中落地的案例。
第三个阶段的发展契机是分布式事务系统的成熟,发展出来和大数据存算分离类似的架构。大家发现利用分布式 KV 或 NewSQL 的事务能力,能够极大简化各类系统元数据的设计。这已经成为近几年非常大的一个技术趋势。在这个趋势下,包括分布式 KV 和 NewSQL 系统在内的分布式事务系统,在几乎所有的存储领域,逐渐取代掉了原有的每个系统自行维护的元数据存储。同时,有了这样一类系统后,在上面去实现元数据的计算逻辑就变得非常简单,原来在实现上非常复杂的操作,利用 ACID 的特点可以简单地包装成 SQL 或者 KV 语句。
分布式事务系统已经在理论和工程上很好的解决了均衡性、规模等诸多问题,可以扩展到非常大的规模。在文件系统领域,这种架构以 Facebook 的 Tectonic 为典型代表的第三阶段系统,在实践上已经能够做到千亿级规模。但是,这类系统也会有一个比较大的缺陷,就是它极度依赖底层分布式事务系统提供的事务能力,对于写操作而言不是非常高效的实现,性能上达不到文件系统领域很多业务的要求。除了写性能比较弱,其他方面可以优化到比较好。
3 CFS 元数据服务架构
3.1 元数据服务的关键问题对于在云上构建大规模分布式系统而言,从规模角度上,刚才提到的第三阶段分离式架构是最理想的,但是其他方面并不能满足现实业务的要求,所以百度智能云的文件存储 CFS 在这个架构基础上,又做了一些改进,让整个系统达到比较好的状态。
3.2 一个文件创建的例子在前面整个元数据服务的建模中,我们提到关联变更是一个非常关键的问题,这里进一步展开描述一下这个要求在实现上是怎么做到的。
以分离式架构为代表,让我们来看一个文件创建的过程。假如需要在 A 目录下创建一个名为 f2 的文件,需要经历以下步骤:
- 首先需要读目录本身,并对目录上锁(②);
- 在系统中插入 f2 文件的记录(③);
- 根据关联变更的要求,更新 A 目录的属性(④);
- 整个操作完成后,对 A 目录解锁,以及 commit 整个操作(⑤)。
上锁过程是为了保证整个操作是原子完成的,在整个过程中没有其他操作能够读到正在进行的脏数据。可以看到,上锁的时间几乎和整个请求的处理时间一样,临界区非常大。
如果尝试把锁去掉会有什么后果呢?我们看一个并发的例子。在目录下创建两个文件,一个是 f2,一个是 f3,这两个操作几乎同时进行。假设系统有个 children 字段,维护了目录下有多少子项。这两个操作进行前,A 目录下还没有任何文件和目录,所以看到的 children=0。这两个操作都会对 A 目录添加一个子项,所以 children 会加 1。这时就会产生问题,因为两个操作互相没有看到对方的存在,系统中也没有任何互斥机制,就会导致这两个操作分别写下了 children=1 的错误结果。这个错误会产生一个严重的后果,当删除 A 目录下任何一个文件时,children 就会减到 0,系统会认为 A 目录是空的!如果此后再执行一个删除 A 目录的操作,系统检查发现 children=0 放行了,A 目录就被成功删掉。但此时 A 目录下实际上还残留了一个目录,这个目录永远不可达到了。这个状态违背了文件系统层级目录要求,产生了孤儿节点。从这个例子可以看到,锁操作在整个系统中是保证关联变更正确性的关键手段。
3.3 CFS 的核心思路:对分离式架构进行无锁化改造
我们在平时写代码时,应该有所体会,一个大的临界区对整个系统性能伤害是非常大的。在文件系统里,这会导致跟该目录相关的所有操作都需要做很重的排队。百度智能云的文件存储 CFS 对这个问题的解决思路,和我们去优化一段代码中大的临界区是类似的,即通过不断缩小锁的区间,来达到整个架构近似无锁化的效果。为了达到这一点,CFS 在实现上采取了一系列手段的组合:首先,通过优化元数据的布局,将冲突的范围缩小到很小的范围——一个元数据分片的粒度上;然后,进一步将冲突范围从单条或多条记录缩小到字段级别;最后,在前面两个操作的基础上,成功地将元数据代理层消除掉了,将分离式架构做的比较差的点优化到和耦合式架构一样的效果。
3.4 优化数据布局接下来,简单介绍下每一部分怎么做的。
我们在研究文件系统元数据时候发现,每个目录项可以分为两个部分:id 记录和 attr 记录。id 记录负责实现点读中的路径查找和范围度,解决如何从父目录的 inode 和名字找到自己的问题;attr 记录存的是文件或目录本身的属性信息,如修改时间,属性字段等。
CFS 首先在架构上对这两部分做了分离,把每个目录项的两个部分分开存储,然后在这基础上做一些亲和性的处理。我们发现,关联变更的要求的实质是同时更改父目录的 attr 记录和子项的 id 记录。如果在元数据分片上做亲和处理,把目录项的 id 和它父目录的 attr 亲和到同一个分片上,就可以把冲突的临界区缩小到一个分片的范围内。
在实现上,我们将这两种数据混排在同一张数据库表中,以<父目录 inode, 名字>为主键表示 id 部分,以<目录 inode, /ATTR>表示目录的 attr 部分,/ATTR 中的 / 是特殊字符,不会出现在文件或目录的名字中,因此不会产生冲突和混淆。这样表示之后,父目录的 attr 和它的子项 id 的表示可以统一到<id, name>这种形式,在数据分布上是连续的。再配合以 id 范围来划分元数据分片的规则,就可以达到将关联变更涉及到的数据收敛到同一个分片的效果。
在这基础上,我们又观察到,文件在整个系统中是比较特殊的。首先,文件下面不会再有子项。其次,文件的元数据操作和数据操作的频率有关联,做数据读写时候,就会伴随文件属性操作。
基于这个观察,我们在布局上做的另一件事情是把所有的文件属性,从元数据服务中拆出来,和数据服务放在一起。数据服务的规模一般都要远比元数据服务大,通过这个调整,在降低元数据服务压力的同时,我们让文件的元数据操作处理能力变得更强。
调整完数据布局之后,我们在操作顺序上也做了一些调整,让临界区真正缩小到分片的范围。以文件为例,在创建文件时,首先会在数据服务中创建文件的 attr 记录,然后在元数据分片上创建 id 记录,同时更改父目录属性。做路径查找时候,是从 id 记录开始从根一级一级往下查找,都是先 id 记录后 attr 记录,刚好修改操作查询操作顺序是完全颠倒的。这样就使得我们有机会去掉保护整个操作的临界区,因为调整后的修改顺序保证了修改中间的脏数据不会被其他客户端看到,即使操作失败,也不会对正确性造成任何影响。在 CFS 中,我们对所有的 POSIX 操作做了分类,对顺序进行了合理编排,从而使除 rename 之外的所有写操作的冲突集中到单分片上。
3.5 单分片原语对单分片而言,当一个事务没有多个参与者的时候,可以从原始 2PC 事务优化到 1PC 事务,但是 1PC 事务对于文件系统而言还有提升空间。对于文件系统,所有修改冲突来自父目录属性,父目录属性归根到底只有两类操作修改,一类是加减操作,一类是赋值操作。加减操作比如维护 children 的数量,赋值操作比如修改时间的覆盖。
这两类操作与我们平时所做的原子操作非常类似,所以在 CFS 中,做的第二个事情是在元数据系统这一层抽象出两个通用的机制,将整个记录的冲突缩小到字段范围。这两个机制称为 Delta Apply 和 Last-writer-win。Delta Apply 会把并发在进行的多个操作的修改合并到一起,比如 A 操作加 1,B 操作加 1,可以直接整合到加 2 的操作。Last-writer-win 就是则是做无脑覆盖,始终用更后来的数据覆盖之前的数据。通过这两个机制,我们可以把原来在单分片上有冲突的 1PC 事务,通过字段层面的优化,让他们并发执行,合并提交。
3.6 移除元数据代理层优化完数据布局,引入单分片原语后,实际上已经实现了让整个执行路径无锁化的目的。我们进一步发现,经过这些优化后,在分离式架构里,元数据代理层存在的意义不是很大了。元数据代理层所做的工作主要是两个:一个是对文件系统的接口做翻译,转化成事务请求;第二个是做一些冲突的处理,使放行到底层分布式事务系统的冲突更小。经过前面两步优化,鉴于冲突在整个系统层面已经不存在了,可以让客户端去做绝大部分的工作。所以,在百度智能云的文件存储 CFS 中,第三个比较大的改动就是,除了一部分 slow-path Rename(因为 rename 在操作过程中涉及的语义非常复杂,不详细展开)会由一个单独的服务承担,其他请求全部由客户端直接发起。这样去掉了元数据代理层之后,整个 I/O 路径的长度跟单点架构、耦合式架构是一样的,因此延时方面的性能也能和他们达到同一水准。
3.7 CFS 整体架构经过上面的铺垫,现在我们可以很容易推导出百度智能云的文件存储 CFS 采取的架构。在这个架构里,客户端 ClientLib 承载了绝大多数 POSIX 语义处理的操作,将操作进一步分为 4 种,转发给不同的子系统进行处理。
文件数据操作(图中的 File Data Semantic)和文件属性操作(图中的 File Attribute Semantic)是由数据服务 FileStore 来做处理。
元数据操作进一步拆解为 Slow-path Rename(图中的 Rename Semantic)和其他部分(图中的 Namespace Semantic)。Slow-path Rename 包括同目录文件rename 外的所有 rename 操作,会由一个单独的 rename 服务来处理,这个服务会对 rename 操作做适当的排序,防止系统中出现成环、孤儿节点等 bad case。除此之外,其他所有的元数据相关操作,全部由客户端直接发到元数据底座,即图中的 TafDB,来进行处理。TafDB 是百度沧海·存储团队自研的一套分布式 KV 系统,文件存储 CFS 和对象存储 BOS 都在使用这个系统存储元数据。
3.8 测试数据
从这一页的测试数据,我们可以看到无论对写操作还是读操作,整个系统可以轻松扩展到百万级别,这在整个文件系统领域是非常难的事情,但在我们系统中通过前面的创新做到了这一点。我们在「百度智能云技术站」微信公众号以及 EuroSys 2023 发表的论文上,对整个架构有更多的描述,如果大家感兴趣,可以进行扩展阅读。
以上是今天分享的全部内容。
Q&A
Q:CFS 中有类似 CephFS 的目录动态迁移么?
CephFS 属于耦合式架构,其架构用大数据领域做类比,就是存算一体的架构,做目录动态迁移时候,需要同时迁移逻辑和数据。在生产环境,做这样的迁移代价是非常大,可能会有很长时间的服务中断,因此生产环境中很少用这个技术。
CFS 支持目录动态迁移。CFS 的整体架构是分离式架构,类似大数据中存算分离的架构,语义层是在外围做的,迁移时候只需要对底层数据迁移。这个迁移实际上是由 TafDB 元数据系统自动做的,CFS 不感知。TafDB 内部根据负载情况做分片的分裂、合并和均衡,保证了处理过程中服务不中断。
Q:递归删除怎么支持?
这是 POSIX 和 HDFS 一个比较大的不同点。POSIX 没有递归操作,假如需要递归删除,是客户端的行为,客户端拿到文件或目录列表,一个个删除。所以,CFS 中没有递归删除的问题。Q:请解释下数据布局的分片规则,如何将冲突缩短到单分片的?
关联变更操作中产生冲突的是父目录的 attr 记录和子项的 id 记录,为了缩小这个冲突到单分片 CFS 做了几个事情。
首先,CFS 将存储在 TafDB 中的数据以<id, name>为主键,这样会把同一目录下所有 id 记录汇聚到一起,存储的时候是连续的。
其次,我们对于目录的属性做了特殊处理。对于目录属性而言,id 就是自己的 inode,名称用了一个保留名字,叫 /_ATTR。这样做一方面保证不会和子项的名字产生冲突,另一方面也让父目录 attr 记录、子项的 id 记录连续到一起了。
最后,TafDB 做分裂时候以 id 为分裂规则,使父目录的 attr 和目录项的 id 不会分拆到不同分片上,从而让操作冲突范围收敛到了单个分片上。Q:CFS 有没有针对 NVMe SSD 做一些优化性的设计?
有的。在元数据架构部分体现不出来这部分优化,NVMe SSD 属于单机引擎层面的内容。TafDB 是运行在 NVMe SSD 上的,针对这类高性能硬件也做了比较多的软件优化。
Q:百亿级小文件,文件是预先生成好的,推荐哪些开源方案?可选 MongoDB 吗?
文件系统领域用得比较多的开源系统是 CephFS 。CephFS 做动态均衡的能力比较弱,但既然文件预先生成好了,如果提前能规划好目录,让不同目录能合理 pin 到不同的元数据节点上,在某些场景下是一个可以接受的方案。
Q:回收站机制是如何考虑?
删除操作在 TafDB 里把记录删除了,这时候其实是一个软删除,删除后用户就看不到文件了。删除的文件由后台机制去做文件数据 GC 的操作,在 GC 操作执行前,数据其实是有机会捞回的。目前,CFS 没有在产品上没有暴露这个能力,也没有保证被删数据保留的时长,未来可以考虑将它做成一个正式的回收站功能。
– – – – – – – – – – END – – – – – – – – – –
更多信息请关注 DataFunTalk ~