腾讯云 x JuiceFS:基于 FoundationDB 的企业级统一存储实践

随着大数据和 AI 业务发展,越来越多企业开始从存算一体架构转向存算分离架构。但在实际落地中,企业往往同时使用 COS、OBS、S3、HDFS 等多类存储系统,不同后端在协议、鉴权、文件系统语义和元数据性能上存在差异,给统一接入、数据利旧和缓存加速带来了挑战。

针对这些问题,腾讯云团队基于 JuiceFS + FoundationDB 构建了一套企业级统一存储方案,既能为新建文件系统提供完整的 POSIX 语义,也能将已有对象存储和 HDFS 数据纳入统一命名空间,实现存量数据的统一访问与缓存加速。

其中,FoundationDB 作为元数据引擎,支撑百亿级元数据管理和强一致事务,这也是 JuiceFS 社区首次分享将 FoundationDB 用于元数据管理的实践;本地缓存与分布式缓存则用于降低远端访问延迟。后续,团队还将围绕湖仓一体、AI 训练加速、智能分层和跨集群联邦等方向持续演进。

01 背景与挑战:存算分离之后,存储接入变复杂了

在前期客户沟通和 POC 过程中,我们发现,企业从存算一体迁移到存算分离时,最关注的并不是架构形态本身,而是两个更实际的问题:现有数据和业务能否平滑接入,切换后性能是否还能接近原有存算一体架构。

企业往往同时使用 COS、OBS、S3、HDFS 等多类存储系统。不同后端在访问协议、SDK、鉴权方式和文件系统语义上存在差异,如果 Spark、Hive、Flink 或 AI 训练框架需要分别适配,客户端开发和维护成本会迅速上升。

性能也是存算分离落地中的关键挑战。数据访问路径变长后,远端存储和网络访问会引入额外开销;而多存储并存又使缓存能力难以统一复用。因此,统一存储方案不仅要解决多后端接入问题,还要提供可复用的缓存加速能力。

此外,对象存储虽然适合海量数据存放,但在 liststat、目录遍历和小文件访问等元数据操作上,通常不如传统文件系统高效;不同后端对目录、权限、配额和快照等语义的支持也并不一致,这些差异会进一步影响大数据和 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 训练框架无需分别适配底层存储。

image

在元数据访问路径上,我们将 JuiceFS 的元数据访问链路服务化。客户端不直接连接 FoundationDB,而是通过 RPC 与无状态元数据服务交互。元数据服务统一承载元数据 API、认证鉴权、多卷管理、配额管理、目录保护、文件保护和任务管理等能力,并通过 ZooKeeper 完成服务注册、服务发现和跨节点状态同步。元数据服务可以按需水平扩展,FoundationDB 则作为其后端元数据引擎,负责元数据持久化和事务处理。

image

为了同时覆盖新建文件系统和已有数据接入场景,底层存储层提供托管模式和外部模式。

  • 托管模式主要面向新建文件系统。文件元数据通过元数据服务写入 FoundationDB,文件数据按照 JuiceFS 的 chunk / slice / block 模型切分后写入对象存储,从而提供完整的 POSIX 文件系统语义。
  • 外部模式主要面向已有数据利旧场景,其核心是 UFS(Unified File System)抽象层。UFS 将 COS、OBS、S3 和 HDFS 等后端抽象为统一的文件系统接口,但不迁移或接管后端已有的数据和元数据,也不使用托管模式的切片存储路径。元数据类请求由元数据服务通过 UFS 透传到底层存储,数据读写则通过相应后端的原生协议完成。

image

统一命名空间将两种模式组织在同一套目录结构中。对上层应用来说,看到的是统一的路径和访问入口;对底层实现来说,不同目录既可以对应托管文件系统,也可以挂载已有的 HDFS 或对象存储数据。这样,新增数据与存量数据可以在同一套访问体系下共存,而无需先完成大规模数据迁移。

在数据访问路径上,系统同时提供本地缓存和分布式缓存。读请求优先查询本地缓存,本地未命中后访问分布式缓存,最后再回源到底层存储,从而降低远端访问延迟和后端存储压力。

03 FoundationDB 实践:面向百亿级元数据的设计取舍

在统一存储接入层中,元数据引擎决定了系统的规模上限和一致性能力。当文件数量达到十亿甚至百亿级时,元数据系统本身就会成为整个架构的关键瓶颈。

为什么选择 FoundationDB

在元数据引擎选型阶段,我们重点关注几个问题:单集群是否能够支撑百亿级元数据规模,事务模型是否足够强,是否支持自动分片和横向扩展,以及运维复杂度是否可控。

传统关系型数据库在单卷规模和横向扩展上容易遇到瓶颈,因此我们重点评估了分布式 KV 方案,其中也包括 TiKV 和 FoundationDB。TiKV 在行业内已有不少大规模元数据场景实践,也具备较强的横向扩展能力;FoundationDB 则在严格事务一致性、有序 Key-Value 模型、自动数据分布、多副本强一致以及运维复杂度方面更符合我们的需求。

最终,我们选择 FoundationDB 作为元数据引擎,主要是因为它更适合承载文件系统元数据这类强一致、高并发、可范围扫描的访问模式。与此同时,这次实践也让团队积累了 FoundationDB 在大规模元数据场景下的建模、调优和运维经验,为后续 Hive 库表元数据等更多场景提供了参考。

Key 设计:多卷隔离与范围扫描

文件系统元数据天然具有两类访问特点:一是同一集群需要承载多个文件系统,要求不同卷之间有清晰边界;二是目录遍历、属性查询、chunk 索引查询等操作经常依赖前缀扫描。因此,Key 设计既要满足多卷隔离,也要尽量贴合文件系统的访问路径,避免在大规模场景下引入热点或大事务问题。

基于 FoundationDB 的有序 KV 特性,我们延续了 JuiceFS 元数据设计思路:以 fsname 作为 Key 前缀区分不同文件系统;将同一文件相关的属性、目录项、chunk 索引等元数据组织在相近 Key 空间内,提升访问局部性;数值字段采用大端序编码,使字典序与数值顺序保持一致,便于顺序扫描。

image

在此基础上,多个文件系统可以共用同一个 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 集群可以同时承载多个文件系统。新增文件系统时,也不需要重启元数据服务,可以在线完成扩展。

image

在多节点部署下,还需要同步配额、权限、目录保护、任务状态等运行时信息。我们通过 ZooKeeper 实现跨节点变更通知,并结合防抖合并、按 Key 精准刷新和定时轮询兜底机制,保证配置变更能够及时同步到各个元数据服务节点。

通过无状态元数据服务、fsname 前缀隔离和 ZooKeeper 状态同步,系统可以在保持多租户隔离的同时,实现服务层和元数据层的横向扩展。

06 百亿级元数据验证

我们基于 JuiceFS 1.3.0 和 FoundationDB 7.3 进行了百亿级元数据测试。测试环境采用 6 台 x86 服务器,操作系统为 Kylin V10 SP3,FoundationDB 采用 triple 三副本和 SSD 存储引擎部署。

这组测试在已经写入 108 亿条元数据的基础上进行压测。结果显示,在百亿级数据规模下,FoundationDB 的单操作延迟与基准数据基本持平,部分操作如 readdir_1klookup 甚至表现更好。整体来看,单卷元数据规模达到 108 亿后,元数据操作延迟仍可以保持在亚毫秒到 2ms 级别,说明大规模数据量下没有出现明显性能衰减。

image

在极限写入测试中,我们通过 15 个并发进程执行 juicefs clone 克隆元数据,验证 FoundationDB 的写入上限。测试结果显示,在 6 台机器 + SSD 配置下,稳定阶段写入吞吐约为 11,000~15,000 inode/s。从瓶颈分析来看,主要压力集中在 Storage 写入队列和磁盘 I/O,而不是 CPU 或网络。后续可以通过增加 Storage 进程、提升磁盘性能,或扩展 Commit Proxy、TLog 等组件进一步提升读写能力。

image

高可用方面,我们分别验证了 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 和对象存储之间自动迁移,从而在性能和成本之间取得更好的平衡。对于更大规模的云上部署场景,也会继续推进跨集群联邦能力,实现多集群之间的元数据同步、路由和统一访问。

未来,我们会继续围绕性能、成本、弹性和数据治理能力迭代,让更多业务能够以统一方式访问和管理底层数据。

posted @ 2026-07-31 14:38  JuiceFS  阅读(66)  评论(0)    收藏  举报