Curve 如何演进 (1):从 EuroSys'23 CFS 论文看文件系统
背景
CFS 是百度智能云文件存储,是一个大规模分布式文件系统。EuroSys'23 CFS 论文主要介绍了该系统的元数据核心设计,对长期困扰文件系统元数据领域的 Posix 兼容性和高扩展性(特别是写扩展性)难以兼顾的问题进行了解答。
为了帮助读者更了解该系统的创新过程,百度存储团队发表了一篇文章《如何将千亿文件放进一个文件系统,EuroSys'23 CFS 论文背后的故事》,从文件系统的概念和需要解决的关键问题说起,非常详细地介绍了设计理念、演进过程。
我们在阅读论文和文章解析的过程中受益匪浅,CFS 跟 Curve 文件系统在设计上有一些理念是相同的,也有很多地方值得 Curve 借鉴、学习。本文的目的是从 CFS 的设计理念出发,对 Curve 做一次回顾,查漏补缺。
关键点解析
元数据服务的架构
这一点,之前我们有测试过使用 TiKV 作为元数据引擎,对比 Curve 文件系统原生的引擎,同样发现,使用分布式 KV 在标准测试 mdtest 下的性能并不好,是 Curve 文件系统原生引擎性能的一半。文章在[2.5 对分布式锁性能影响的定量分析中]提到,在没有锁冲突的情况下,锁在整个操作中的耗时占比也达到了 50%+。
NameSpace1.0
元数据操作需要用到的数据库的能力可以概括为:按照 secondary key 查找指定项,按照 parent_id list,子项和 parent_id 的事务操作。Curve 文件系统, 所有信息可以认为都是存储在分布式 KV 上(Curve 文件系统使用 Raft+Rocksdb 作为元数据)的持久化引擎,虽然没有使用数据库作为引擎,但 Curve 文件系统通过一定的放置策略加速上述提到的几个能力,以提高元数据性能:每个数据分片负责一定范围的 inodeid, 所有的 dentry 存放在 parent_inodeid 所在的分片上。
下面我们来对比下 CFS 和 Curve 文件系统在 lookup 和 readdir 两个读操作:
lookup /A/B
Namespace1.0 查找路径:先在中查找 [rootinodeid + filenameA] 的项,获取/A 的 inodeid;再使用 [/A 的 inodeid + filenameB] 获取 /A/B 的 inode。涉及到两次查表操作。
Curve 文件系统查找路径:先通过 [rootinodeid + filenameA] 去 rootinodeid 所在的分片上,获取/A 的 dentry,解析出 inodeid;再使用[/A 的 inodeid + filenameB]去 /A 所在的 inode 上获取 /A/B 的 dentry,解析出对应的 inodeid。同样也是涉及两次 rpc 操作。
readdir /A
在 Namespace1.0 中查找路径:先查找 [rootinodeid + filenameA] 的项,获取 /A 的 inodeid,再通过 db 操作,list parentid 为 /A 的 inodeid 的所有项。需要经过 db 两次查询操作。
Curve 文件系统查找路径:通过 [rootinodeid + filenameA] 去 rootinodeid 所在的分片上,获取/A 的 dentry,解析出 inodeid;再去[/A 的 inodeid]所在的分片获取 parentid 为/A 的 inodeid 的所有项。同样涉及到两次 rpc 操作。
关于写操作,对比 mknode 和 rename 两个写操作:
mknode
Namespace1.0 的操作路径:在 filestore 中写入文件属性的相关信息;将文件的 dentry 信息{parent_id, name, ...} 插入 TafDB 并修改 parentid 的 关联信息,这一步通过 TafDB 的分布式事务能力完成。涉及到两次 rpc 操作。
Curve 文件系统操作路径:选择一个分片写入文件属性相关信息;将文件 的 dentry 信息插入到 parent_id 所在的分片中,并修改 parent_id 的 nlink 等属性信息,这一步通过本地的 RocksDB 的事务接口完成。涉及两次 rpc 操作。
rename
Namespace1.0 可以直接通过 TafDB 的分布式事务接口完成。
Curve 文件系统,由于 rename 涉及到删除源文件的 dentry, 在新的 parent_id 下创建新的 dentry,涉及到多个不同分片上的操作,为此设计了额外的事务处理逻辑。
Namspace2.0
上一小节说明了 Curve 文件系统的 dentry、inode 放置策略,这个设计也正好符合文中提到的抽象模型:影响元数据服务扩展性的根源是更新父目录属性时产生的冲突,这是关联变更的一部分。这里提到的父目录的属性,指的就是 inode 相关的属性。
但是在工程实践上,Curve 文件系统,对于同一个分片上的请求,是做不到像数据库这样使用同步原语解决。比如,对于 create dentry 的操作,在底层需要执行的操作是:
操作 1:创建 dentry,在 rocksdb 中插入一条记录
操作 2:修改父目录的 nlink,并修改 mtime,将新记录插入到 rocksdb 中
在并发情况下,这两个操作是无法通过同步原语的方式解决的,目前只能做到串行操作。这是影响性能非常关键的一点。对于这一点,从上层看从减少父目录的更新次数可以做请求的合并。长期看,提高 partition 上多类操作的并发度是非常必要的,比如一个很常见的 AI 场景,数据集的批量拷贝性能会大大受限。
在单个目录下文件数量过多的场景,Curve 文件系统做不到让它续展一个分片,因为当前分片上负责的 inode 范围是固定的。可以通过针对目录子项过大的情况,让新的 inode 不在创建的方式做兜底,后续也许可以考虑通过增加虚拟目录节点的方式来做迁移。
Curve 文件系统,对 rename 做了两个版本的优化,都是使用的 2PC 事务,第一个版本 rename 在同一个文件系统上是全局排队的,第二个版本 rename 在同一个文件系统上源和目的不同可以并发。并没有考虑到从业务场景做一些优化。在看到上述统计信息的时候,很快就关联到在业务使用中很多时候都是从 rename A.tmp A 这类在同一目录下,为了避免一致性使用临时文件再做 rename 的场景。这也是 Curve 文件系统可以借鉴的 rename 优化途径。另外,我们发现 percolator 这样的事务模型在文件系统这样的场景下略显<笨重>,后续我们考虑使用事务管理器的方式来为一些操作提供原子性保障。
在工程优化上不仅考虑通用优化手段,也结合业务特征进行优化是 Curve 文件系统需要持续改进的部分。
总结
CFS 为性能做了大量设计和工程实践上的优化,我们通过分段解析,也看到了 Curve 文件系统很多可以优化的点,后续会持续跟进,也欢迎感兴趣的小伙伴的加入。如果上述的理解和描述有疏漏或者错误的地方、或者有一些新的想法,都欢迎在 Curve 社区指正和交流。
------ END. ------
🔥 开发者活动:
🔥 用户案例:
Curve 文件存储在 Elasticsearch 冷热数据存储中的应用实践
扬州万方:基于申威平台的 Curve 块存储在高性能和超融合场景下的实践
🔥 技术解析:
Curve v2.7 发布:支持 Hadoop SDK,助力大数据存储降本提效
关于 Curve
Curve 是一款高性能、易运维、云原生的开源分布式存储系统。可应用于主流的云原生基础设施平台:对接 OpenStack 平台为云主机提供高性能块存储服务;对接 Kubernetes 为其提供 RWO、RWX 等类型的持久化存储卷;对接 PolarFS 作为云原生数据库的高性能存储底座,完美支持云原生数据库的存算分离架构。
Curve 亦可作为云存储中间件使用 S3 兼容的对象存储作为数据存储引擎,为公有云用户提供高性价比的共享文件存储。
GitHub:https://github.com/opencurve/curve
官网:https://opencurve.io/
用户论坛:https://ask.opencurve.io/
微信群:搜索群助手微信号 OpenCurve_bot
版权声明: 本文为 InfoQ 作者【OpenCurve】的原创文章。
原文链接:【http://xie.infoq.cn/article/3266c6595b9b233f62750c069】。文章转载请联系作者。
评论