随着大数据和 AI 业务发展,越来越多企业开始从存算一体架构转向存算分离架构。但在实际落地中,企业往往同时使用 COS、OBS、S3、HDFS 等多类存储系统,不同后端在协议、鉴权、文件系统语义和元数据性能上存在差异,给统一接入、数据利旧和缓存加速带来了挑战。
针对这些问题,腾讯云团队基于 JuiceFS + FoundationDB 构建了一套企业级统一存储方案,既能为新建文件系统提供完整的 POSIX 语义,也能将已有对象存储和 HDFS 数据纳入统一命名空间,实现存量数据的统一访问与缓存加速。
其中,FoundationDB 作为元数据引擎,支撑百亿级元数据管理和强一致事务,这也是 JuiceFS 社区首次分享将 FoundationDB 用于元数据管理的实践;本地缓存与分布式缓存则用于降低远端访问延迟。后续,团队还将围绕湖仓一体、AI 训练加速、智能分层和跨集群联邦等方向持续演进。
01 背景与挑战:存算分离之后,存储接入变复杂了
在前期客户沟通和 POC 过程中,我们发现,企业从存算一体迁移到存算分离时,最关注的并不是架构形态本身,而是两个更实际的问题:现有数据和业务能否平滑接入,切换后性能是否还能接近原有存算一体架构。
企业往往同时使用 COS、OBS、S3、HDFS 等多类存储系统。不同后端在访问协议、SDK、鉴权方式和文件系统语义上存在差异,如果 Spark、Hive、Flink 或 AI 训练框架需要分别适配,客户端开发和维护成本会迅速上升。
性能也是存算分离落地中的关键挑战。数据访问路径变长后,远端存储和网络访问会引入额外开销;而多存储并存又使缓存能力难以统一复用。因此,统一存储方案不仅要解决多后端接入问题,还要提供可复用的缓存加速能力。
此外,对象存储虽然适合海量数据存放,但在 list、stat、目录遍历和小文件访问等元数据操作上,通常不如传统文件系统高效;不同后端对目录、权限、配额和快照等语义的支持也并不一致,这些差异会进一步影响大数据和 AI 应用的统一访问体验。
基于这些需求,我们最终选择以 JuiceFS 构建面向企业场景的统一存储方案。
02 整体架构:统一接入与双模式设计
JuiceFS 采用元数据与数据分离的架构。元数据引擎可以根据业务规模和一致性需求进行选择,例如 Redis、TiKV 等;我们选择 FoundationDB,主要利用其强事务、有序 Key-Value、自动数据分布和横向扩展能力,支撑百亿级元数据管理。
基于 JuiceFS 的基础架构,我们构建了由统一客户端、服务化元数据层、多级缓存和多后端存储适配组成的统一存储方案。JuiceFS 提供文件系统语义、多协议访问、对象存储数据路径和本地缓存等基础能力;在此基础上,我们进一步扩展统一鉴权、多卷管理、统一命名空间、已有数据接入和分布式缓存等能力,以满足云服务场景下的多租户和多存储需求。
在客户端侧,大数据和 AI 应用可以通过 HDFS 接口、POSIX 挂载、S3 接口和 Python SDK 等方式访问系统。客户端支持 Hadoop SDK、Python SDK、FUSE 挂载、本地缓存和服务发现,使 Spark、Hive、Flink、Presto 以及各类 AI 训练框架无需分别适配底层存储。
在元数据访问路径上,我们将 JuiceFS 的元数据访问链路服务化。客户端不直接连接 FoundationDB,而是通过 RPC 与无状态元数据服务交互。元数据服务统一承载元数据 API、认证鉴权、多卷管理、配额管理、目录保护、文件保护和任务管理等能力,并通过 ZooKeeper 完成服务注册、服务发现和跨节点状态同步。元数据服务可以按需水平扩展,FoundationDB 则作为其后端元数据引擎,负责元数据持久化和事务处理。
为了同时覆盖新建文件系统和已有数据接入场景,底层存储层提供托管模式和外部模式。
- 托管模式主要面向新建文件系统。文件元数据通过元数据服务写入 FoundationDB,文件数据按照 JuiceFS 的
chunk / slice / block模型切分后写入对象存储,从而提供完整的 POSIX 文件系统语义。 - 外部模式主要面向已有数据利旧场景,其核心是 UFS(Unified File System)抽象层。UFS 将 COS、OBS、S3 和 HDFS 等后端抽象为统一的文件系统接口,但不迁移或接管后端已有的数据和元数据,也不使用托管模式的切片存储路径。元数据类请求由元数据服务通过 UFS 透传到底层存储,数据读写则通过相应后端的原生协议完成。
统一命名空间将两种模式组织在同一套目录结构中。对上层应用来说,看到的是统一的路径和访问入口;对底层实现来说,不同目录既可以对应托管文件系统,也可以挂载已有的 HDFS 或对象存储数据。这样,新增数据与存量数据可以在同一套访问体系下共存,而无需先完成大规模数据迁移。
在数据访问路径上,系统同时提供本地缓存和分布式缓存。读请求优先查询本地缓存,本地未命中后访问分布式缓存,最后再回源到底层存储,从而降低远端访问延迟和后端存储压力。
03 FoundationDB 实践:面向百亿级元数据的设计取舍
在统一存储接入层中,元数据引擎决定了系统的规模上限和一致性能力。当文件数量达到十亿甚至百亿级时,元数据系统本身就会成为整个架构的关键瓶颈。
为什么选择 FoundationDB
在元数据引擎选型阶段,我们重点关注几个问题:单集群是否能够支撑百亿级元数据规模,事务模型是否足够强,是否支持自动分片和横向扩展,以及运维复杂度是否可控。
传统关系型数据库在单卷规模和横向扩展上容易遇到瓶颈,因此我们重点评估了分布式 KV 方案,其中也包括 TiKV 和 FoundationDB。TiKV 在行业内已有不少大规模元数据场景实践,也具备较强的横向扩展能力;FoundationDB 则在严格事务一致性、有序 Key-Value 模型、自动数据分布、多副本强一致以及运维复杂度方面更符合我们的需求。
最终,我们选择 FoundationDB 作为元数据引擎,主要是因为它更适合承载文件系统元数据这类强一致、高并发、可范围扫描的访问模式。与此同时,这次实践也让团队积累了 FoundationDB 在大规模元数据场景下的建模、调优和运维经验,为后续 Hive 库表元数据等更多场景提供了参考。
Key 设计:多卷隔离与范围扫描
文件系统元数据天然具有两类访问特点:一是同一集群需要承载多个文件系统,要求不同卷之间有清晰边界;二是目录遍历、属性查询、chunk 索引查询等操作经常依赖前缀扫描。因此,Key 设计既要满足多卷隔离,也要尽量贴合文件系统的访问路径,避免在大规模场景下引入热点或大事务问题。
基于 FoundationDB 的有序 KV 特性,我们延续了 JuiceFS 元数据设计思路:以 fsname 作为 Key 前缀区分不同文件系统;将同一文件相关的属性、目录项、chunk 索引等元数据组织在相近 Key 空间内,提升访问局部性;数值字段采用大端序编码,使字典序与数值顺序保持一致,便于顺序扫描。
在此基础上,多个文件系统可以共用同一个 FoundationDB 集群,同时保持各自独立的 Key 空间。对于 list 等可能扫描大量数据的操作,则通过分页处理控制单次事务规模;事务冲突场景下,结合乐观锁和自动重试减少锁等待。
如何规避 FDB 的硬限制
FoundationDB 对 key、value 和事务大小都有明确硬限制:单个 key 最大 10KB,单个 value 最大 100KB,单个事务总大小最大 10MB。这些限制不能通过参数调整,因此在将其作为文件系统元数据引擎时,需要特别关注几类容易放大元数据规模的操作:例如频繁随机写、反复 truncate、fallocate punch hole、copyFileRange 等,可能导致单个 value 持续增长;大范围文件拷贝、大文件截断、批量元数据导入或删除整个文件系统,则可能触发事务过大的问题。
其中,风险最高的是 chunk slices 累积。在 JuiceFS 的数据模型中,一个 chunk 对应的 slice 列表会存储在同一个 value 中,每个 slice 约占 24 字节。当同一个 chunk 下累积到约 4,266 个 slices 时,就可能触及 FoundationDB 的 100KB value 限制。频繁随机写同一个 chunk、反复 truncate、fallocate punch hole、copyFileRange 等操作,都会持续增加 slice 数量。如果缺少治理机制,单个 value 就可能不断膨胀,最终导致事务提交失败。
这个问题在实现上还有一个隐蔽点:部分路径会使用 AppendIfFits 这类原子追加操作。它属于盲写,事务内无法提前知道 append 之后的 value 总大小;如果 append 后超过 100KB,往往要到事务提交阶段才会失败。这意味着问题不会在写入前及时暴露,而是随着 slice 静默累积,在某次提交时突然触发。因此,仅依赖失败后的重试并不够,还需要在数据模型和写入路径上提前设置安全边界。
为了解决 chunk slices 累积问题,我们补齐并强化了 compact 机制。核心思路是在读写路径中提前发现 slice 数量过多的 chunk,并将多个小 slice 合并为更少的大 slice,从而控制单个 value 的增长。对于轻度碎片化的 chunk,可以异步触发 compact,避免阻塞正常写入;当 slice 数量达到安全阈值时,则同步触发 compact,防止继续增长到 100KB 限制。实践中,maxSlices 设置为 2500,对应约 60KB,为 100KB 上限预留了约 40KB 的安全空间。
在实现 compact 时,还需要处理并发和回收问题。合并过程通过 CAS 语义确认 chunk 没有被并发修改,避免数据丢失;如果 compact 失败,不影响正常写入,可以在后续触发时重试。合并后产生的旧 slice 则进入 GC 流程,通过引用计数判断是否仍被使用,并结合延迟删除机制支持误删恢复和后台回收。
除了 chunk slices 累积,我们也排查了文件系统配置、Kerberos Token、POSIX 锁、Xattr 扩展属性以及 UFS 路径类 key 等其他风险项。整体来看,这些风险相对可控:大部分 value 规模较小,key 设计也受到文件名或路径长度约束。真正需要重点治理的,仍然是 chunk slices 累积导致的单 value 膨胀问题。
04 缓存加速体系:用两级缓存降低远端访问开销
存算分离之后,数据访问路径从本地磁盘变成了远端对象存储或 HDFS。对于大数据分析和 AI 训练这类读密集场景,如果每次读取都直接回源到底层存储,网络延迟和后端访问压力都会被放大。因此,统一存储层除了要解决“如何接入多种存储”,还需要解决“如何让远端数据读得更快”。
我们的思路是引入两级缓存:客户端本地缓存作为 L1,分布式缓存作为 L2。读请求会先查询本地缓存,命中后直接返回;本地未命中时,再访问分布式缓存;如果分布式缓存仍未命中,才回源到底层存储,并将数据回填到缓存体系中。这样既能利用客户端本地 SSD 或内存提供最低延迟,也能通过独立的 SSD 缓存集群,在多个客户端之间复用热点数据。
本地缓存主要沿用 JuiceFS 社区已有能力,并扩展支持外部模式。它运行在客户端进程内,默认以 block 为粒度缓存数据,支持 LRU 淘汰、顺序读预取和 OS Cache 控制。分布式缓存则是我们自研的独立缓存集群,客户端通过一致性哈希将相同数据块路由到固定缓存节点,更适合多节点共享读、客户端本地空间有限,或者需要跨任务复用热点数据的场景。
缓存体系真正需要重点处理的是一致性。托管模式下,每次写入都会生成新的 Slice ID,缓存 key 随之变化,旧缓存自然不会被再次命中;同时对象存储中的底层对象写入后不会被原地修改,因此不需要额外的主动失效机制。
外部模式则更复杂,因为底层 HDFS 或对象存储中的文件可能被外部系统直接覆盖。如果缓存 key 只包含路径,就可能读到旧数据。为此,我们在外部模式中引入 fingerprint 机制,将文件路径、block 偏移以及文件指纹一起纳入缓存 key。fingerprint 由文件修改时间和长度组成,并在 open 时冻结;当文件被外部修改后,fingerprint 发生变化,旧缓存自然不命中,从而满足 close-to-open 一致性。
这也带来一个取舍:托管模式基于 Slice ID 实现更细粒度的缓存隔离,而外部模式依赖文件 fingerprint,粒度更偏文件级,缓存失效范围可能更大。但对于已有数据可能被外部系统修改的场景,这是保证一致性所必须接受的代价,也是后续可以继续优化的方向。
除了基础读缓存,系统还支持缓存预热、防击穿、TTL 驱逐和透明降级。例如,可以通过 warmup 命令提前预热热点数据;分布式缓存可以按目录前缀批量预热;当缓存层不可用时,系统也可以自动降级为直接读取底层存储,避免缓存故障影响业务读取。
05 多租户与弹性扩展
在云厂商场景下,统一存储层需要同时服务多个业务、多个租户和多个文件系统。因此,我们将元数据服务设计为无状态架构,客户端通过 ZooKeeper 完成服务发现和负载均衡,元数据服务节点可以按需水平扩展,从而提升整体访问能力和可用性。
多卷隔离则依赖 FoundationDB 的 Key 前缀设计。每个文件系统使用独立的 fsname 前缀,不同卷之间拥有独立的 Key 空间,使一个 FoundationDB 集群可以同时承载多个文件系统。新增文件系统时,也不需要重启元数据服务,可以在线完成扩展。
在多节点部署下,还需要同步配额、权限、目录保护、任务状态等运行时信息。我们通过 ZooKeeper 实现跨节点变更通知,并结合防抖合并、按 Key 精准刷新和定时轮询兜底机制,保证配置变更能够及时同步到各个元数据服务节点。
通过无状态元数据服务、fsname 前缀隔离和 ZooKeeper 状态同步,系统可以在保持多租户隔离的同时,实现服务层和元数据层的横向扩展。
06 百亿级元数据验证
我们基于 JuiceFS 1.3.0 和 FoundationDB 7.3 进行了百亿级元数据测试。测试环境采用 6 台 x86 服务器,操作系统为 Kylin V10 SP3,FoundationDB 采用 triple 三副本和 SSD 存储引擎部署。
这组测试在已经写入 108 亿条元数据的基础上进行压测。结果显示,在百亿级数据规模下,FoundationDB 的单操作延迟与基准数据基本持平,部分操作如 readdir_1k、lookup 甚至表现更好。整体来看,单卷元数据规模达到 108 亿后,元数据操作延迟仍可以保持在亚毫秒到 2ms 级别,说明大规模数据量下没有出现明显性能衰减。
在极限写入测试中,我们通过 15 个并发进程执行 juicefs clone 克隆元数据,验证 FoundationDB 的写入上限。测试结果显示,在 6 台机器 + SSD 配置下,稳定阶段写入吞吐约为 11,000~15,000 inode/s。从瓶颈分析来看,主要压力集中在 Storage 写入队列和磁盘 I/O,而不是 CPU 或网络。后续可以通过增加 Storage 进程、提升磁盘性能,或扩展 Commit Proxy、TLog 等组件进一步提升读写能力。
高可用方面,我们分别验证了 1 台和 2 台机器故障场景。在 1 台机器故障时,服务保持可用,FoundationDB 会自动触发副本修复和数据迁移;在 2 台机器故障时,服务仍然可用,但集群容错能力会降为 0,不能再容忍更多故障。故障恢复后,系统会触发数据重新均衡,测试中完整恢复时间约为 4 小时。这个过程会带来额外 I/O 压力,因此生产环境需要重点关注磁盘水位、单盘故障和恢复期资源消耗。
容量规划方面,测试数据显示,50 亿 inode 时 FoundationDB 磁盘占用约 6.1TB,108 亿 inode 时约 10.1TB。综合三副本、压缩效果和预留空间后,可以按 约 120GB / 亿 inode 作为基础容量估算。
通过这组验证,我们基本确认:FoundationDB 可以支撑单卷百亿级元数据规模,并在已有百亿级数据的情况下保持稳定的元数据访问性能。同时,三副本架构具备较好的故障恢复能力,但生产环境仍需要做好容量规划、磁盘监控和恢复期 I/O 管理。
07 小结与未来规划
目前,这套基于 JuiceFS + FoundationDB 的统一存储接入层,已经在大数据存算分离场景中完成了核心能力建设,包括统一客户端接入、外部数据利旧、多级缓存加速、百亿级元数据管理以及多租户扩展能力。后续,我们会继续围绕湖仓一体、AI 训练和存储治理等方向演进。
在湖仓一体方向上,我们计划基于统一存储层加强与 Iceberg、Hudi 等表格式的集成,让上层数据湖和数据仓库场景能够复用统一的存储接入、权限管理和缓存加速能力。
AI 训练方面,后续会进一步优化缓存预热和就近缓存能力,降低训练任务中的远端读放大和 GPU 等待时间。同时,也会结合快照能力探索模型 checkpoint 管理,让训练过程中的中间状态保存、恢复和复用更加高效。
在存储治理方向上,我们还会探索智能分层能力,根据数据访问频率在 SSD、HDD 和对象存储之间自动迁移,从而在性能和成本之间取得更好的平衡。对于更大规模的云上部署场景,也会继续推进跨集群联邦能力,实现多集群之间的元数据同步、路由和统一访问。
未来,我们会继续围绕性能、成本、弹性和数据治理能力迭代,让更多业务能够以统一方式访问和管理底层数据。