logo

百度沧海·存储 Mantle 系统架构演进之路,SOSP'25 论文背后的故事(一)

作者:xxinjiang2026.01.30 16:01浏览量:11

简介:百度沧海·存储 Mantle 系统架构演进之路,SOSP'25 论文背后的故事

在技术深水区,最大的障碍往往不是未知,而是那些我们深信不疑的已知。
这篇文章清晰还原了创新的真实路径:问题从何而来、传统方案为何失效,以及 Mantle 新的系统设计思路是如何一步步成形的。

在技术高度成熟的存储领域,百度沧海·存储团队做了一件极具勇气的事:回归第一性原理,持续地怀疑一切被视为理所当然的假设。

他们不再迷信所谓的行业标配,当这些标准开始成为性能负担,他们选择推翻教科书式的最优解,重构系统边界,用跨层协同设计,重新找回效率的本质。

这不仅是一次技术突围,更是对惯性思维的决裂。它真实呈现了创新是如何发生的:在扩展性与局部性的张力中反复权衡,基于真实负载而非技术教条,做出关键取舍。

正文共计 1 万 5 千余字,建议留出一段完整的时间阅读。

在 AI 与大数据爆发的时代,云存储面临新的挑战:如何在保持对象存储低成本优势的同时,构建出能超越 HDFS 性能的文件系统语义?

SOSP’25 上的 Mantle 论文给出了百度智能云的答案。论文链接:https://dl.acm.org/doi/epdf/10.1145/3731569.3764824

本文将讲述那些论文里没写的幕后故事:我们是如何打破惯性思维,在一个成熟的技术领域里走出一条新路的。希望这篇架构演进的复盘,能给业界提供一份有价值的参考。同时,本文也系统性地补充了 Mantle 的完整设计思想,其中部分关键设计取舍和实现细节,受限于论文篇幅,未能充分展开。

1. 背景

多年以来,HDFS 一直是大数据存储的代名词。然而,随着数据规模的日益增大,其固有缺陷愈发凸显:首先,三副本机制导致存储成本高昂。其次,传统单 NameNode 架构将文件规模限制在数亿级别,难以满足 AI 时代单桶百亿甚至更大规模的扩展性需求。最后,复杂的运维工作对技术团队提出了极高要求。

在此背景下,具备「低成本、无限扩展、云原生免运维」等优势的对象存储,迅速成为构建新一代数据湖存储底座的共识。然而,随着大量大数据分析、AI 训练等数据驱动的计算业务在对象存储上铺开,新的瓶颈开始出现:在很多场景下,对象存储的性能远弱于 HDFS。

问题的根源在于,大数据计算严重依赖文件系统语义,而传统对象存储采用的「平坦 Namespace」对此支持极其不友好。在这种模式下,系统不具备真正的目录逻辑,而是以对象的全路径作为 Key 进行元数据存储。这导致所有看似简单的目录类操作,都会被放大为成百上千次的底层对象操作,性能急剧下降。具体表现为:

  • Read dir 效率极低:HDFS ls /logs/ 操作,只需查找 /logs 目录的子节点列表(如 2023/、2024/),瞬间完成。而对象存储没有“目录”概念,ls /logs/ 操作会遍历所有以 /logs/ 为前缀的全路径 Key。这意味着为了找出下面的子目录,系统可能需要扫描数亿个文件(如 /logs/2023/01/01/file.txt……)的索引,造成了巨大的读取放大,效率极其低下。

  • Move 代价高昂:Move 是大数据计算(尤其是 Spark 作业)的常用操作。在平坦 Namespace 下,将目录 a/ 重命名为 b/,需要对 a/ 目录下的 N 个对象,逐一执行 Copy + Delete 操作,最终的操作量被放大了 2*N 倍。当目录下文件数成千上万时,这几乎是不可接受的。

为了弥补这一核心缺陷,业界普遍认为,为对象存储增加「层级 Namespace」能力是必然趋势。但遗憾的是,当前主流云厂商的方案始终未能摆脱单机性能瓶颈,其扩展性与 HDFS 相比并无本质差异,一个真正高性能、可扩展的解决方案仍然缺位。

2. 业界探索:分布式 Namespace 的三代演进

分布式 Namespace 的演进历程,本质上是为了追求扩展性,而被迫逐步放弃元数据局部性的过程。这一过程虽然解决了扩展性的瓶颈,却在云对象存储场景遇到了新的痛点。

2.1. 架构演进的本质:牺牲「局部性」换取「扩展性」

阶段一:单点元数据架构

局部性完美,扩展性受限。代表系统如 HDFS NameNode。虽然单点架构确保了低延迟,但其数亿级的文件上限无法支撑 AI 时代的百亿级需求。
1769672301793.jpg

阶段二:子树分区架构

折中方案,热点问题难以规避。代表如 CephFS。试图通过按目录子树切分来兼顾扩展与局部性,但在生产环境中极易形成访问热点(Hotspot),且动态迁移极其复杂。
1769672321648.jpg

阶段三:目录分区架构

扩展性优秀,局部性丧失。代表系统如 Tectonic,将目录树按照目录粒度打散存储到分布式数据库的多个分片中。这是目前分布式层级 Namespace 架构的主流演进趋势。它比较好的解决了扩展性和热点问题。

方案原理:以百度内部类 Tectonic 系统(CFS Namespace 1.0)为例,其表结构设计如下:

  • 主键:<parent_id, name>,负责实现 lookup 和 readdir。
  • 二级索引:<inode_id>,负责实现目录的 getattr。

表结构示意:
1769672349307.jpg

事务化操作:Namespace 的操作被转化为数据库事务。例如 create file2 操作(添加元数据并修改父目录属性),可表示为一个包含 INSERT 和 UPDATE 的分布式事务。

1769672361515.jpg

2.2. 局部性丧失带来的三大性能痛点

然而,目录分区架构是一把双刃剑。在获得无限扩展性的同时,破坏了元数据的「空间局部性」,导致了严重的性能退化,具体表现为 3 大痛点:

  • 高并发目录更新冲突:create、unlink 等操作不仅需要插入子节点元数据(如 <3, E>),还强制要求同步更新父目录(如 <1, C>)的 mtime 或 nlink 等属性。在目录分区架构中,父子节点极大概率分布在不同分片,这导致上述操作退化为昂贵的跨分片分布式事务(2PC)。更严重的是,当高并发地在同一目录下创建 / 删除文件时,所有请求都会争抢更新父目录的同一行属性记录,引发激烈的分布式锁竞争,导致系统吞吐量急剧下降。
    1769672419122.jpg

  • 路径解析高开销:路径上的每一层目录元数据可能分布在不同服务器上,路径解析必须依次进行多轮 RPC 交互,如下图所示,lookup /A/C/E 可能导致 3 次 RPC:客户端先基于已知的根目录 ID 远程查询 A,拿到 A 的 ID(RPC 1),再用 A 的 ID 远程查询拿到 C 的 ID(RPC 2),最后才能查到 E(RPC 3)。这种跨节点、串行的「接力式」查找,使得 RPC 次数随着路径深度线性增长,导致延迟居高不下。
    1769672438680.jpg

  • 小规模场景性能不佳:分布式架构固有的通信与事务开销,在小规模(单机可承载)场景下反而成为了累赘。以简单的 rename /A/C/E/ A/E 操作为例,在单机系统中,这仅需在本地内存中修改指针即可完成。然而在目录分区架构中,由于源父目录(/A/C)与目的父目录(/A)往往分布在不同分片,这一操作退化为昂贵的跨分片事务。这种为了扩展性而牺牲局部性的设计,导致在不需要大规模扩展的小场景下,其性能表现反而显著弱于传统的单机方案。
    1769672455057.jpg

2.3. 业界主流优化方案及其局限性

为了解决上述痛点,业界涌现了多种优化方案。纵观近几年的技术演进,业界(如 CFS 和 InfiniFS)主要聚焦于攻克「高并发目录更新冲突」和「路径解析高开销」这两个核心难题,而对于「小规模场景性能不佳」的问题,一直未被妥善解决,甚至被视为分布式的「必要代价」。而在云对象存储这个严苛的场景下,即便针对前两个痛点的优化方案,也依然存在未解决的问题。我们可以从问题导向的视角,来简单回顾两个最新的代表性工作:

2.3.1. 解决高并发目录更新冲突:CFS(Eurosys’23)

百度智能云的文件存储 CFS 试图解决的一个重要问题是:如何消除因并发更新父目录属性而引发的锁竞争。它主要通过属性分离和冲突合并两个优化机制来实现:前者在物理上消除了跨分片事务,后者在逻辑上消除了锁竞争。

机制一:属性分离
CFS 通过「属性分离」思想,将同目录创建操作由两阶段提交(2PC)优化为一阶段提交(1PC)。具体来说,CFS 通过策略强制父目录的属性信息(每个父目录一个特殊的 /_ATTR 子节点)与其他子节点落在同一个物理分片上。有了这个物理共存的基础,原本昂贵的跨分片事务(2PC)就被优化成了单分片事务。

举例说明:create /A/C/E

如下图所示,假设目录 C 的 ID 为 3,其父目录 ID 为 1。目录 C 的属性被剥离出来,作为一个特殊的子节点存储,Key 为 <ParentID=1, Name="/_ATTR">。

新文件 E 的元数据 Key 为 <ParentID=1, Name="E">。结果:利用 TafDB 的分片策略(Policy-based Sharding),所有 ParentID=1 的记录被强制分配在同一个物理分片中。因此,更新 /_ATTR 和插入 E 变成了 单分片 1PC 事务。

1769672502557.jpg

机制二:冲突合并

虽然属性分离将冲突缩小到了单分片,避免了分布式事务的开销,但在同目录并发更新情况下,对 ATTR 节点的更新仍需排队,未经优化只能做到数千 QPS,CFS 利用 TafDB 的特性实现了冲突合并:对于 links、children、size 等数字属性,引入了「Delta Apply」机制,利用属性更新操作(如 +1, -1)的结合律,将同一批次内的多个并发更新在内存中聚合成一个总增量,对于 mtime、permission 等属性,则采用 Last-Writer-Win 策略,直接保留最新的赋值操作的结果,通过这两种方式,只需获取一次锁即可完成批量更新。

1769672616072.jpg

但是通过深入分析后,我们发现这个方案在对象存储场景依然存在以下痛点:

未解决跨目录 Rename 引发的属性冲突:CFS 的单分片优化无法覆盖跨目录 Rename 场景。在对象存储的大数据计算场景(如 Spark 作业提交)中,会爆发大量跨目录 Rename 操作(将临时目录文件移动到最终输出目录)。这些操作天然打破了单分片边界,引发源目录与目的目录之间激烈的属性更新冲突,导致性能严重退化。

关于 CFS Namespace 的更多细节可参考沧海存储团队的公众号文章:如何将千亿文件放进一个文件系统,EuroSys’23 CFS 论文背后的故事

2.3.2. 解决路径解析高开销:InfiniFS(FAST’22)

InfiniFS 试图解决的一个问题是:如何避免深度为 N 的路径解析产生 N 次 RPC。

机制一:乐观访问的客户端元数据缓存

InfiniFS 采用乐观的客户端缓存机制,旨在消除路径解析带来的额外开销。具体而言,该机制允许客户端缓存元数据,且服务端不主动推送失效通知,仅在客户端访问失败时才触发被动更新。因此,在缓存命中时,路径解析的开销几近于零。

但是通过深入分析后,我们发现这个方案存在以下痛点:

  • Rename 操作引发的全局广播开销:目录重命名操作要求系统必须通过 Rename Coordinator 节点广播通知所有元数据节点(MDS)废弃相关缓存。在大规模集群中广播开销是灾难性的,且性能会随着节点的增多进一步退化。

  • 云对象存储场景的架构不匹配:最根本的冲突是,云对象存储采用的 Restful API 机制以及无状态 Proxy 架构,导致这套用于分布式文件系统的客户端缓存方案在对象存储场景难以落地。
    1769672721759.jpg

机制二:基于预测执行的并行路径解析

为了规避逐层查找导致的串行等待,InfiniFS 引入了预测执行的并行路径解析机制。它的逻辑是:客户端利用可预测目录 ID 算法(Hash (父目录 ID, 目录名, 版本号)),预先计算出路径中所有中间目录的 ID,然后并发向 MDS Server 发起校验。简单说,就是试图用「并发流量」换「解析速度」,但是在深路径和高并发场景下会引入以下问题:

  • 资源开销大:对于深层嵌套的路径,Client 需要瞬间建立并维护大量 TCP 连接,CPU 和网络开销巨大。

  • 长尾问题明显:并发发出去的请求,整体耗时取决于「最慢的那一个」。

Mantle 论文的实验数据(参见 Figure 17)证实了上述结论:在 512 个客户端的高并发场景下,相比传统的逐层 Lookup ,InfiniFS 引入的预测执行并没有表现出显著优势。这说明此时资源争抢带来的损耗,已经抵消了并行化带来的理论收益。
1769672993097.jpg

2.4. 总结:对象存储场景下的核心矛盾

综上所述,虽然「阶段三」的架构解决了扩展性问题,但当我们将 InfiniFS 和 CFS 等方案置于云对象存储的严苛环境(Restful API、无状态 Proxy)中时,它们依然无法成为最终答案。现有的优化方案虽然在各自领域有所突破,但始终无法同时解决以下 3 个核心挑战,其根源在于分布式扩展性破坏了「元数据局部性」,且对象存储的架构限制了常规的补救路径:

  • 局部性重建受阻(缓存失效):InfiniFS 等方案试图通过「有状态客户端缓存」来重建被破坏的局部性。然而,对象存储的「Restful API、无状态 Proxy」特性直接否定了这一补救方案的可行性,导致长路径解析开销无法通过传统手段消除。

  • 事务冲突加剧:CFS 的单分片优化在面对对象存储在大数据场景常见的跨目录 Rename 等复杂操作时失效。

  • 长尾与小规模性能退化:分布式带来的通信与事务开销,使得在小规模场景或简单负载下,性能反而不如单机方案。

此时摆在我们面前的似乎是一个死结:想要扩展性就得切分数据(牺牲局部性导致性能受损),想要高性能就得聚合数据(牺牲扩展性)。是否有一条架构设计的新路,能够跳出这个非此即彼的怪圈?

带着问题,百度沧海・存储团队开启了架构探索之路。在此演进历程中,我们通过 Mantle 和 MantleX 两代架构的持续演进,分别针对「局部性重建」与「规模自适应」进行攻坚,最终逐一解决了上述三大难题。

3. Mantle 核心架构的演进

正如前文所分析的,无论是传统的客户端缓存,还是 InfiniFS 的并行解析,都与云对象存储的 Restful API 以及无状态 Proxy 架构存在根本性冲突,无法直接应用。

我们意识到必须另辟蹊径:重建「路径解析操作所需元数据」的局部性。要将多次网络往返(Multi-RPC)的路径解析优化为单次网络往返(Single-RPC),然而这条路,我们走得并不平坦,经过了多个阶段艰难的 Trade-off,最终才演化出这套架构:

3.1. 路径抉择:为何放弃子树划分方案

我们一度开始怀疑:是不是基于分布式数据库的「目录划分」架构,本身就不适合对象存储场景?如果问题的根源在于「目录被彻底打散」导致的局部性丧失,那么,一个看似顺理成章的退路是:回到「子树划分」架构,例如 HDFS Federation。然而,在对这一方案进行深入推演后,我们最终还是放弃了这个技术路线,原因主要有两个:

  • 首先是技术层面的热点难题。「子树划分」很容易出现访问热点而影响可用性,一旦热点形成,唯一的办法就是进行「动态子树分裂和调度」。然而,动态子树分裂在工程上的实现复杂度极高。

  • 更深层次的,是该方案违背了我们「统一元数据架构」的规划。过去多年,我们的技术路线一直是基于 TafDB 构建上层的平坦和层级 Namespace。而「子树划分」方案要求我们彻底绕开 TafDB 这个基石,去构建一套完全独立的、用于动态子树分区的全新存储体系。这不仅意味着高昂的重复研发成本,更会导致未来更大的运维复杂度和技术栈碎片化。

3.2. 扩展性和性能的 Trade-off

放弃「子树划分」后,我们重新审视了生产环境的负载特征,一个被忽视的关键数据浮出水面:目录节点在海量元数据中的占比极低,通常不足 10%。

这意味着,即便面对百亿级对象的超大规模场景,其目录节点数量也仅在十亿量级。这个规模,完全处于单机可承载的范围内。既然如此,我们能否做一个大胆的取舍?用「够用」的扩展性(单机存下所有目录),换取路径解析性能加速。

基于此,Mantle 的核心架构雏形初现:

中心化索引(IndexNode):引入一个中心节点存储所有目录信息,专门负责加速路径解析,重建「路径解析操作的局部性」。
水平扩展存储(MetaStore):全量元数据继续交由底层成熟的 TafDB 集群管理,保障线性扩展能力,这部分可以复用过去我们在 CFS Namespace 的经验。
这个方案规避了「子树划分」的两个痛点:首先,它不再需要复杂的「动态分裂」,其次,它可以最大程度地复用我们现有的 TafDB 技术体系。Mantle 架构的雏形诞生了。

3.3. 数据同步机制抉择

确立了「IndexNode + MetaStore」的双组件架构后,我们立刻面临一个新的难题:这两个组件之间的数据该如何同步?我们面临着 2 种架构路径的抉择:

  • 路径 1:异步更新(最终一致性)对目录修改操作友好,但代价是 MetaStore 的数据永远滞后。这迫使 IndexNode 必须成为目录元数据的唯一可信源,不仅要负责路径解析,还得独自承载 readdir 和 stat 等所有目录查询流量。

  • 路径 2:同步更新(强一致性)引入两阶段提交(2PC)来保证两者写入的原子性。虽然目录修改操作引入了 2PC,但换来了架构红利:既然数据强一致,我们就可以将除路径解析外的目录相关读操作(如 readdir、DirectoryStat),卸载(Offload)给底层可线性扩展的 TafDB 集群,从而最大限度地缓解了 IndexNode 的单点读瓶颈

最终决策:同步更新

这一决策的依据,依然源于我们对负载的洞察:目录修改操作(mkdir, rmdir 等)的占比极低(<10%)。

Trade-off 逻辑至此闭环:我们用少量目录修改操作(mkdir /rmdir)的性能损耗(2PC),换来了以下 2 个收益:

  • 高效的解析性能:IndexNode 将复杂的长路径解析优化为单次 RPC。

  • 查询负载的卸载:强一致性机制让我们能将 readdir 和 stat 等操作卸载给底层 TafDB,显著降低了 IndexNode 的负载压力。

新的困境:异构架构的工程挑战

逻辑上虽完美,但工程落地上却让我们面临了严峻挑战:

  • 重复造轮子的代价:云存储都是多租户访问的,一个租户(Namespace)需要对应一个 IndexNode。为了实现高可用和水平扩展,我们需要从零打造一套类似 TiKV 的 Multi-Raft 架构,即让一个 Raft Group 对应一个租户索引,一个存储节点上会有多个 Raft Node。这意味着我们要同时维护 IndexNode 和 MetaStore 两套完全不同的分布式存储栈。

  • 异构一致性的挑战:IndexNode 与 MetaStore 架构异构且物理隔离,跨系统实现鲁棒的 2PC 事务及故障恢复机制,工程复杂度极高。

1769673210753.jpg

3.4. 分层解耦与跨层协同设计的抉择

上述问题的根源在于我们将目录服务和 TafDB 视为了两个独立的异构系统。这启发了我们:不如打破边界,将两者融为一体。

这正是 Mantle 跨层协同设计(Co-design)思想的起源。其核心决策在于:打破目录树语义层与底层分布式 KV 存储的边界,实现深度融合。

从纯粹的软件工程角度看,将文件系统的语义逻辑下推到分布式事务 KV 内部,无疑引入了「耦合」。若严格遵循分层解耦,构建一套独立的分布式 IndexNode 系统,看似架构清晰,实则会面临工程实现复杂度高和异构系统一致性难以保障两大挑战。因此,我们做出了一个关键的权衡:选择可控的耦合,来换取工程效率的提升。

这一决策的达成过程异常艰难。团队因为担心这种将「业务逻辑下沉」的耦合设计会导致系统日后无法维护,一度想放弃该路线。为了打破僵局,我们快速实现了一个原型。当定义好协处理器(Coprocessor)接口并初步实现 lookup、目录 rename 操作后,一切开始变得清晰:需要下推到 TafDB 执行的核心逻辑其实非常精简,最多只有几百行代码。这意味着耦合程度是完全可控的。

为了彻底消除对系统可维护性的顾虑,我们还规划了长远的演进路线:计划引入目录树 DSL(领域特定语言)。通过在受控沙箱中执行 DSL,既能保留下推执行的性能红利,又能实现逻辑层面的优雅解耦。

最终方案确立:IndexNode 不再是外部系统,而是 TafDB 内部的一个特殊分片。
1769673360764.jpg

这一同构设计带来了显著的收益:

  • 复用 TafDB 原生事务机制:当目录修改涉及 IndexNode 和 MetaStore 时,我们直接利用 TafDB 内置的高效分布式事务,无需再为异构系统开发复杂的两阶段提交机制。

  • 基于存算协同的网络优化:利用协处理器(Coprocessor)机制,我们将路径解析等逻辑下推至存储节点。原本需要多次网络往返的操作,被压缩为一次 RPC。

  • 为 MantleX 埋下伏笔:这种同构设计,不经意间为后续从「单机模式」平滑演进到「分布式模式」(MantleX)奠定了最关键的架构基石。

至此,我们完成了逻辑闭环:从分区热点问题出发,通过简化模型、引入强一致同步,最终意识到问题的根源在于系统异构,从而演进到了将计算与存储深度融合的「协同设计」架构。

Mantle 架构正是这一系列思考的最终产物。它通过引入一个中心化的目录索引节点(IndexNode) 来实现目标,其核心优势在于:

  • 用中心化的目录索引重建了读取的局部性:由于完整的目录节点都存在于单个 IndexNode 节点内部,客户端解析任意深度的路径时,只需向该 IndexNode 发送一次 RPC 请求。通过这种方式,Mantle 在分布式环境中完美地重建了路径遍历操作的局部性。

3.5. Mantle 架构的额外优势

除了加速路径解析,Index 分片的引入还意外的带来了其他优化的机遇:

  • 加速目录 rename:有了 Index 分片 ,我们就可以把目录 rename 执行过程中触发的分布式成环检测优化为单机成环检测,从而极大地提升目录 rename 的性能。

  • 加速写操作:由于 Namespace 之间是互相隔离的,一个 Namespace 对应一个 Index 分片,这样我们就可以把时钟服务 offload 至 Index 分片,从而减少从 TSO 获取时钟这一次 RPC 开销。

剩余部分请看下文

相关文章推荐

发表评论

活动