之江实验室: 如何基于 JuiceFS 为超异构算力集群构建存储层 ?
之江实验室就是上述科研创新的推动者,实验室由浙江省政府主导、浙江大学等院校支持、企业参与的事业单位性质的新型研发机构,为材料、基因、制药、天文、育种等科学领域的研究提供新的方法、工具和手段。
由于算力资源的天然异构性,以及不同技术实现的计算能力往往出自不同的系统架构或指令集,会导致软件不兼容性,从而提高了算力使用的门槛,也使得算力利用率难以有效提高。为解决这个问题,之江实验室将各种异构算力资源汇聚在一起,形成一个庞大的“算力池”。本文将分享之江实验室如何基于 JuiceFS 为超异构算力集群构建存储层的实践。
数字反应堆是之江实验室一个大型科研装置。整个科研装置由软硬件部分组成。在软件方面,负责研发之江瑶光智能操作系统。
图片该智能操作系统主要包含两个关键组成部分。首先,它提供了通用的计算平台解决方案,为上层应用层提供支持。通过这个平台,用户可以针对不同的应用领域,如计算材料、计算制药、计算天文等,进行开发和应用。
其次,我们实现了一个异构资源聚合方案。在之江实验室,我们拥有多个异构集群,包括 CPU 集群、GPU 集群以及一些超算资源。这些不同类型的计算资源具有不同的使用方式。通过异构资源聚合方案,我们将这些不同的计算资源统一聚合,实现统一的管理和使用。
图片整体架构如上,在之江实验室的计算与数据中心,我们部署了多套异构集群,包括 H3C 的傲飞集群、之江的计算集群、国产的曙光集群等,以及边缘计算场景,这些都纳入了我们的管控系统中。通过集群设备插件的方式,我们对这些不同的集群进行了统一抽象,并实现了算力的聚合。在整个异构算力联邦的体系中,我们将这些不同的算力集群都抽象为 Kubernetes(k8s)集群进行管理。
上层下发的业务指令及不同类型的作业,通过元调度器来决定将这些作业发送到哪个集群。根据不同的调度策略,如计算优先级、能耗优先级和性能优先级,来确定计算作业的具体执行方式。目前,我们已接入约 200P(PFLOPS, 1P 相当于每秒执行一千万亿次浮点运算) 的 AI 算力和 7000 核的 HPC 算力。
首先:存储层的抽象和统一。因为在许多计算场景中,包括超算和 AI 训练,都使用 POSIX 接口。因此,我们希望在这一层面上统一使用 JuiceFS 的接口来提供服务。
第二个方面:存储方案的通用性。目前接入的算力集群是异构的,那么尽量需要考虑方案在不同的异构集群中都能适用。
第三个方面:数据的编排条件。我们的数据是有典型的冷热特性,在一个任务在计算过程中,它用到的数据是热数据,任务计算之后或者过了几天之后,这个数据就变成了冷数据,我们对冷数据的读和操作是比较少的。
第四个方面:存储性能的要求。数据读写性能要好。特别是热数据的读取性能。在算力集群中,计算资源非常宝贵,如果因为数据读取慢导致 CPU,GPU 空转等待,是极大的浪费。
这个方案存在一个较大的问题,即直接使用裸露的对象存储性能非常差。另外,裸露使用对象存储的 S3FS 挂载点经常会出现莫名其妙的丢失。一旦挂载点丢失,容器将无法访问,如果要恢复挂载点,必须对整个容器进行重启,这对用户业务造成了很大的干扰。
由于开始时集群规模较小,而且这套方案部署简单,实验室一开始采用了这种方案进行部署。但随着集群规模的逐渐扩大,尤其是从去年开始建设的数字反应堆,从单一集群到多集群的演进过程中,当节点数量从最初的 10 多台逐渐扩展到 100 多个节点时,这个方案基本上已经行不通了。
经过调研,我们发现该方案的结构相对复杂,涉及到许多组件的组成。之江实验室是一个超异构的多集群环境,由于 Alluxio 并不是一个强一致性的文件系统,它实际上只是一个缓存的粘合层。在这种多集群环境下,会面临元数据不一致的问题,而解决这个问题特别困难。由于上层用户的业务产品非常繁多,我们不能干涉用户的使用方式。在这种情况下,如果不同集群之间的数据不一致,将会导致严重的问题。其次底层仍然使用 OSS,当数据规模扩大到一定程度时,由于 OSS 的元数据性能问题,当存储规模达到一定级别后, 元数据同步、新集群的缓存层初始化等操作也会遇到较大的性能瓶颈。
JuiceFS 有非常详细的社区文档,可以直接上手使用,并且在我们的搭建测试集群以及最终的上线部署中表现出色。另外,JuiceFS 支持 CSI 可以容器化部署,另外对国产硬件的适配性较好。因此我们最终选择将 JuiceFS 作为我们算力侧的存储底座。
首先,JuiceFS 提供了丰富的元数据引擎选择,比如 Redis 和 TiKV,使得 JuiceFS 具有较好的元数据性能。目前我们实验室使用的是由三个节点搭建的 TiKV 作为元数据引擎。由于该配置是去年建立的,现在的性能已经有些不够用,后续我们将逐步提升性能。
最初我们考虑使用 Redis 作为元数据引擎,但后来发现如果使用 Redis,就无法实现水平扩容。从而使用了 TiKV,则可以随着文件系统数量的增长逐步扩展,这确实更好。
第二,在跨集群环境下,使用 JuiceFS 可以实现文件的原子性和一致性。在集群 A 中写入的文件在集群 B 中立即可见。然而,如果使用 Alluxio,就无法做到这一点。Alluxio 需要进行一些数据同步事件等操作才能实现,而这些事件实际上会带来一定的开销。
第三, JuiceFS 具备缓存能力。客户端可以配置一个缓存目录,使用缓存后可以大大降低算力集群对底层存储的压力。
第四,JuiceFS 对于 POSIX 的兼容性非常好。我们发现 Alluxio 的实际兼容性并不那么好,而且其客户端性能相对较一般。Alluxio 可能更适用于不同异构数据源的统一接入层,用于读取数据较好。但是,如果需要频繁写入或修改数据,则可能使用起来并不理想。
第五 JuiceFS 的社区是常活跃。
图片这个是我们自己在实验室环境下测出来的,测试工具:FIO 16 线程、4M Block 、1GB 数据 上图 NAS 的性能就没法看,因为当时测评的时候还在生产环境正在提供服务,当时有大概七十几个节点正在运行,带宽很小,基本就运行不了。
初期,整个高性能计算的过程实际上是分为很多个环节,但是数据分散在不同的存储系统中会带来使用效率和便利性上的挑战。为了简化数据的管理和流转,我们使用了一个统一的存储底座作为存储基础设施。存储底座的核心能力包括高可靠性、低成本和高吞吐量,因此我们选择了对象存储作为存储底座。将数据存储到对象存储中可以轻松实现数据的冷热分层,从而节省存储空间。
然而,直接让计算集群直接使用裸对象存储仍然存在一些问题。首先是元数据性能差的问题,例如对同一目录下文件的列表操作,在文件数量较多的情况下,耗时会非常长。第二个问题是带宽消耗大,数据湖提供的是普通的 IP 网络,而不是 RDMA 高速网络,因此总带宽有限。
图片因此,在对象存储之外,我们还建立了一个元数据集群,并使用了 TiKV 数据库。基于对象存储和 TiKV,我们构建了 JuiceFS 分布式文件系统。算力集群通过在节点上安装 JuiceFS 客户端来读取文件系统的数据。这样,我们可以克服对象存储的一些限制,提高元数据性能,并减少带宽消耗。
为了实现高效的数据流转,我们通过文件管理系统允许用户进行文件的上传和下载操作。文件管理系统底层采用 JuiceFS S3 网关,将数据写入底层存储。
除了数据湖和元数据集群,我们还建立了一个高速缓存集群,它紧密部署在计算集群中,主要目的是为了实现最佳的 I/O 性能。这样解决了计算集群与对象存储数据湖底座之间高效数据流转的问题。用户并不需要关心数据是存储在对象存储中还是在高速缓存集群中。
算力系统对数据流转进行管控。计算集群和高速缓存集群之间通过 200G 的 RDMA 高速网络连接。高速缓存集群上部署了 BeeGFS 高速并行文件系统,将该文件系统作为一个目录挂载到计算集群。这样,计算集群可以像使用本地目录一样使用该缓存系统。
在不同的业务场景中,对存储的需求和性能指标是不一样的。为了能够更高效地服务用户,我们提出了打造存储能力产品化这样的想法,目前 JuieFS 被应用到了以下几类存储产品中。
JuiceFS 会将其数据保存在一个特定的目录下,并根据用户所属的组织架构生成一个唯一的访问路径。通过直接将该路径挂载到容器内,实现数据的隔离。用户可以通过页面端进行文件的上传和下载,也可以使用我们提供的命令和工具对文件进行操作。
在初始建设阶段,通用文件存储存在一个问题,容量的扩展性较差。底层的对象存储集群(oss)的容量有限,随着数据量的增加,用户无法申请更多的存储空间。为解决这个问题,我们引入了存储卷的概念。
存储卷可以类比为云盘,不同的存储卷相当于不同类型的云盘。对于不同的存储类型,我们可以将它们包装成不同的存储卷,以满足用户在不同场景下的需求。
对于需要频繁地读写海量小文件的场景,它需要使用延迟较低且吞吐量较高的存储产品。为满足这种需求,我们将之前搭建的高速缓存集群转化为高速存储卷的功能,直接将其文件系统目录开放给用户使用。这样,用户可以直接使用高速存储,而无需通过 JuiceFS 来访问,可以更直接地感受到高速存储的性能优势。
而对于需要保存大数据但实际上不频繁读取的用户,可以结合 JuiceFS 和对象存储来创建标准存储卷。这样可以提供较大的存储容量和可接受的吞吐性能,同时相对于高速存储卷,支持跨集群的网络互通能力。
此外,一些用户可能对性能有更高的要求,例如他们需要本地盘的产品,但同时也需要数据持久化的能力。在 Kubernetes 场景下,如果用户直接将数据写入本地盘,存在数据丢失的风险,例如遇到意外重启或物理节点问题。在这种情况下,用户需要一种持久化的解决方案。我们可以通过将用户在受影响节点的本地盘开放一部分存储空间作为本地存储卷,并在作业调度时根据用户指定的存储卷将任务调度到指定的节点上。
另外,不同存储产品在容量、吞吐量和跨集群互通能力方面存在差异。例如,高速存储可以在集群内部进行互通,但无法跨集群;存储产品的容量和成本也各不相同。高速存储采用全闪存的集群,建设成本较高,而对象存储的建设成本相对较低,且具备较大的存储容量。因此,将不同的存储硬件(设施)能力包装成不同的存储产品来适配用户不同的业务场景。
在使用 JuiceFS 时,我们还实现了一个数据编排功能。管理员可以将常用的数据集上传到文件系统某个目录,这个目录可以在上层抽象成一个公开的数据集。不同用户在创建作业时都可以挂载这些数据集。普通用户也可以上传自己的私有数据集,并通过 JuiceFS 的预热功能对这些数据集进行预热。
我们在算力集群内部建立了一个高速缓存集群。使用 warmup 指令,用户的数据集可以直接从两端预热到计算节点的高速缓存集群。这样,用户在进行大量模型训练时,可以直接与自己搭建的高性能集群交互,无需与远程 OSS 集群进行交互,从而获得更好的性能。
另外,这种设置可以降低对象存储底座的网络带宽压力。整个缓存的淘汰过程由 JuiceFS 客户端自动管理,因为可以对访问目录进行上限容量的配置。对用户来说,这部分功能相对透明且易于使用。
在我们选择使用 JuiceFS 之后,我们在内部进行了一些文件读取性能的测试,并与算法团队合作进行了测试。当时,从测试结果看,JuiceFS 的读取性能总是比 NAS 慢得多。我们开始查找原因为什么 JuiceFS 比 NAS 还要慢。
后来我们发现,在使用 JuiceFS 和 TiKV 作为元数据的场景中,像列举目录这样的 API 操作实际上是随机的,它并不像 NAS 或其他文件系统那样保证一致的顺序。在这种情况下,如果算法是基于随机选择文件或者代码是固定的,那么可能会认为选择的那些文件应该是固定的。
在处理海量小文件的场景中,元数据的开销是相当可观的。如果元数据没有被缓存到内存中,每次都需要从元数据引擎那里获取,与没有缓存相比这会带来较大的开销。因此,通过这个问题,我们发现在特定的场景下需要对文件的索引目录进行编排。例如,某个算法可能需要处理数十万甚至数百万个文件,如果要确保算法训练的一致性,首先需要将这些文件作为索引文件,作为自己的一个索引目录树。每次进行算法训练时,直接读取索引文件,而不再调用 list dir 的操作,以确保这个文件夹下的文件目录树在算法训练中保持一致性。
编著注: 造成读性能慢主要与用户的使用场景相关,评估后对“目录的随机读”这个功能未作调整,如果其他用户在这个问题上也有类似问题,欢迎提出。
在使用过程中,我们遇到了 TiKV 无法进行垃圾回收的问题。由于我们只使用了这个文件系统,并且显示容量为 106T、1.4 亿个文件,而 TiKV 占用了 2.4T 的容量,这显然是不正常的。
根据官方文档的显示,例如 Redis,大约 1 亿个文件应该只占用大约 30GB 的容量。我们进行了排查后发现可能是 TiKV 的元数据引擎没有进行垃圾回收。我们还查看了报表,发现整个垃圾回收指标为空。可能的原因是我们只部署了 TiKV,而没有部署 TiDB。然而,TiKV 的垃圾回收实际上需要依赖 TiDB,这是一个容易被忽视的问题点。
编著注: JuiceFS 在 #3262 和 #3432 的 PR 中加上了 TiKV 的后台 GC 任务,修复了这个问题。这些修复已经在 v1.0.4 中合入。
当我们挂载 JuiceFS 客户端时,我们将高速缓存集群设置为保存目录,并将其容量设置得相对较高,理论上限为 50T。
在这种情况下,JuiceFS 客户端会定期扫描缓存目录,并建立内存索引,以便 JuiceFS 知道哪些数据位于缓存目录中。因此,这会占用相当多的内存。如果目录非常庞大,我们建议用户关闭这个扫描功能。
在测试小文件随机 I/O 时,我们觉得表现还可以,但在测试顺序 I/O 时,出现了一个较大的问题。例如,使用 dd 命令创建一个 500MB 的文件,结果发现对象存储生成了大量快照。可以看出,这里的存储和对对象存储的操作远远超过了创建一个 500MB 文件应有的操作。
图片在进一步排查时,我们发现在启用 -o writeback_cache 参数后,顺序写会变成随机写,从而降低整体的顺序写性能。该参数只适用于非常高级的随机性场景。如果在不是这种场景下使用该参数,将导致严重的问题。这也是在使用 JuiceFS 时需要注意的一个点。
编著注: 这个问题主要是针对使用 NAS 做缓存的场景,已经在 1.1beta 中优化,扫描时大幅减少内存占用,并提升速度。JuiceFS 在 #2692 中添加了 --cache-scan-interval 来自定义扫描时间,并且可以选择只在启动时扫描一次或完全关闭扫描,用户可以配置该选项。对于使用本地盘做缓存的用户则不需要调整。
我们将提供更多层次的软硬件产品,并将这些能力以不同的存储卷形式进行产品化,以适应用户在不同场景下的存储需求。
目前存在数据安全风险,所有用户的数据都存储在同一个大型文件系统中并通过 hostpath 挂载在裸机。如某些用户拥有节点登录权限,实际上可以访问整个文件系统内部的数据。为了解决这个问题,我们计划在后续采用 CSI 模式结合路径定制来避免隔离性问题。
我们还将上线配额管理功能。在用户使用存储产品时,需要有一种强制手段来限制用户可以使用的存储容量,并且能够准确查看用户实际使用了多少容量。直接使用 du 命令查看容量的过程开销很大,并且不太方便。配额管理功能将解决这个问题。
在计量计费场景下,我们需要了解用户产生的流量和消耗的能量,并根据实际使用的容量进行计费。因此,良好的容量管理能力是必需的。
在使用 JuiceFS 时,我们是直接在物理机上挂载,通过暴露一个监控端口,我们的生产集群可以与这些端口进行通信,并建立了一套监控系统,可以监控和收集所有的监控数据。
数据的容灾和迁移能力目前还比较欠缺。我们遇到了一个典型场景,即现有集群的容量不足,需要上线新的集群。在新旧集群之间,如何处理数据迁移以及不同数据的迁移方式,如何在不影响生产用户的情况下尽量保证业务不中断,实现数据迁移仍然是一个较为困难的问题。因此,我们计划在后续寻找解决方案,以提升这方面的能力
另外,我们还在开发基于 JuiceFS 和 CSI 插件的通用能力,以实现在不同的存储客户端上动态挂载的能力。在生产环境中,用户对挂载参数的调整有一定需求,因为不同的挂载参数适配不同的业务产品。然而,如果直接调整挂载参数,可能会导致整个物理节点的中断。因此,如果能够实现动态挂载的能力,用户只需要对业务进行适当的切换,而无需进行重启等操作。
卷管理能力 (配额、用户、权限)卷能力在之前已经部分实现了配额的功能,但实际上我们更需要的是基于用户和管理人员权限的管理能力。目前,JuiceFS 是挂载在一个大型文件系统上,如果为每个用户创建文件系统,将会带来很高的开销。因此,我们目前采用的是基于一个大型文件系统,通过不同的目录来管理不同用户的权限配合,缺少一个统一的、中心化的用户权限管理系统,仍然需要依赖 Linux 的权限管理。然而,Linux 的权限管理是分布在不同节点上的,对用户权限的管理相对较为困难。我在思考是否可以依赖元数据,并将其作为一个中心化数据库的能力,实现用户和权限的管理。这样,卷的管理能力就可以更加产品化。
支持分布式缓存能力。它可以充分利用计算机的物理资源和本地磁盘,通过集群内的计算节点和网络进行利用。目前我们了解到,商业版有提供这个功能,但社区版也还没有。
挂载点热更新能力。在不同的场景下,用户需要不同的挂载参数,但是卸载和重新挂载的操作太重,会影响用户的业务。用户可以接受短暂的不可读取或者读取中断,但是不能重启业务容器或中断算法或策划程序。我们正在内部调研和开发这个能力。
目录配额需求在 1.1Beta 中已经实现了,详情请大家关注下周的发版信息。分布式缓存和挂载点更新能力,目前商业版可以提供,这两个功能也在社区版的规划当中。 编著注:
直播回顾:
https://www.bilibili.com/video/BV1Fo4y1V7Ka/
洪晨,之江实验室高级工程专员,负责计算侧存储的架构和演进
市值暴涨10519%,原来全世界搞大模型的企业都在给这位华人打工!
百度推出生成式AI代码助手,覆盖 30 种编程语言;高考生喊话马化腾,腾讯回应;机房宕机损失过亿,唯品会负责人被免职 | Q资讯
微信扫码关注该文公众号作者