NAVER 是韩国领先的互联网科技公司运营着韩国最大的搜索引擎并在人工智能、自动驾驶等高科技领域积极布局。作者 Nam Kyung-wan 来自 NAVER Infra 团队自 2023 年参与 JuiceFS 社区代码贡献 (GitHub: kyungwan-nam)为 Hadoop 场景提出了多项改进。本文是作者继“ 为 AI 平台引入存储方案 JuiceFS”后的第二篇博客。NAVER Infra 团队负责运营公共 Hadoop 集群使用 Spark、Hive、MapReduce 等 Hadoop 应用处理数据并将数据存储在 HDFS 中。HDFS 在 Hadoop 生态系统中通过数据本地性支持高性能具备优异的容错性和可扩展性。随着人工智能服务的普及数据规模急剧增长对多样化数据存储的需求也日益增加。同时如何高效地共享 Hadoop 集群外部 AI 平台如 Kubernetes中的数据成为了一项重要挑战。在这一背景下NAVER 探讨了对象存储是否可以替代 HDFS并明确了 JuiceFS 结合对象存储的适用场景。01 HDFS 的局限存储成本上升AI 开发需要以高效且经济的方式存储不断增长的数据并在某些情况下长期保留原始数据以便进行模型改进和重新训练。然而Hadoop 的计算和存储是紧密耦合的导致存储扩展难以独立进行。当没有计算需求时仅为扩展存储空间而增加节点会造成不必要的成本。此外HDFS 默认保留三重副本进一步增加了存储成本。文件数量限制AI 开发涉及数千万个小文件如图像、音频和文本等。HDFS 存在著名的小文件问题因为所有文件和块的元数据都存储在 NameNode 的内存中。例如管理 1000 万个文件大约需要 3GB 的内存。因此HDFS 可管理的文件数量受到单个 NameNode 内存容量的限制。数据中心容灾能力弱HDFS 通常由单个数据中心的节点组成。为应对数据中心故障或灾难需使用额外方案将数据复制到其他数据中心从而产生增加成本。运营成本增加NAVER 由专业人员运营公共 Hadoop 集群负担相对较小但通常 Hadoop 集群的构建和运营非常复杂且成本高昂。若要单独构建和运营稳定的 Hadoop 环境需要专业知识和较高的维护成本。Kubernetes 中的生态兼容性差NAVER AI 平台基于 Kubernetes 构建并利用 Kubeflow、KServe 等多种 AI 开源工具及 GPU 支持。但 HDFS 不支持 POSIX API 和 CSI 驱动无法作为 Kubernetes 常规存储方式即 PersistentVolume使用。因此在 Kubernetes 中使用 HDFS 需在容器中准备 Hadoop 包、配置和认证信息并编写 HDFS API 代码非常繁琐且会降低 AI 开发效率。02 对象存储的优势与劣势Hadoop 通过数据本地性提供高性能但由于 HDFS 与计算节点耦合计算和存储资源难以独立扩展。因此扩展存储空间时仍需增加额外的计算节点。相比之下云环境支持计算和存储的独立扩展。通常数据存储在对象存储中而非 HDFS计算可以通过托管服务如 AWS EMR、Google Dataproc或基于 Kubernetes 的数据处理引擎进行数据则存储在 S3、GCS 等对象存储中。这种架构支持灵活扩展计算和存储资源。此外Hadoop 社区和云供应商提供了 S3A、Azure Blob、Aliyun OSS 等 HDFS 兼容文件系统使得对象存储可以像 HDFS 一样使用。对象存储作为远程存储虽然难以实现数据本地性但具有以下优势存储成本降低计算和存储分离可独立扩展。对象存储通常成本较低并能根据需要选择不同的存储类别。例如对于访问频率低但需长期保留的数据可使用低成本存储类别如 S3 Glacier。出色的扩展性和弹性对象存储设计上支持近乎无限的扩展。对象数量和容量无限制可根据工作负载变化轻松扩展或缩减。数据中心灾难恢复支持S3 等对象存储提供跨区域复制功能可防止数据中心故障或灾难导致的数据丢失。运营成本降低避免 Hadoop 集群的构建和运营负担从而降低运营成本。但对象存储替代 HDFS 是好的选择吗不支持目录在文件系统中文件通过目录进行组织列出目录下的文件是一项基本操作通常速度较快。而对象存储没有目录的概念所有对象是独立的扁平结构。列出文件时需要通过对象前缀搜索速度较慢。此外为模拟目录结构而临时创建的 Directory Marker 对象也会影响性能。不支持重命名在文件系统中重命名是基本操作以 O(1) 级别的原子事务快速执行。但对象存储不支持重命名需通过复制全部数据再删除原数据的方式处理导致速度非常慢且可能中途失败。这一问题对于 MapReduce 和 Spark 等大数据框架影响尤为明显(Apache Hadoop Amazon Web Services support – Committing work to S3 with the S3A Committers)。文件输出操作通常依赖重命名来保证一致性FileOutputFormatCommitter 就是基于重命名实现的。因此在对象存储中直接使用 FileOutputFormatCommitter 会显著降低性能。为了解决这一问题可以使用 Magic Committer它避免了重命名操作并针对对象存储进行了优化。不支持文件权限HDFS 支持 POSIX 权限体系可以设置文件和目录的所有者、组以及其他用户的权限。而对象存储不提供此功能因此文件的所有者和组通常被视为当前用户所有文件和目录的权限默认为 666 和 777即文件可读写目录可读写并可执行(参考: Object Stores vs. Filesystems).。数据访问速度慢对象存储作为远程存储无法保证数据本地性并且每次访问都涉及网络传输因此相较于 HDFS其数据访问速度较慢性能受到网络延迟和带宽限制的影响。Kubernetes 中的低可用性一些工具如 Mountpoint for Amazon S3 和 s3fs支持通过 POSIX API 将对象存储挂载为类似本地文件系统的方式。AWS S3 还通过 Mountpoint for Amazon S3 CSI 驱动 支持将对象存储作为 Kubernetes 卷使用。然而由于对象存储与传统文件系统存在根本差异它无法完全兼容 POSIX API且性能较低。因此在使用这些工具时需要充分了解它们的工作原理和局限性。最终即使在 Kubernetes 环境中使用对象存储低可用性问题仍然无法解决。S3 兼容对象存储的 API 兼容性S3 已成为对象存储的事实标准被多种应用广泛支持。因此许多云供应商和开源项目提供 S3 兼容对象存储。然而S3 兼容对象存储并不完全等同于原生 S3 服务。在使用时需要确认其是否与 S3AFileSystem 或其他应用所使用的 S3 API 兼容。综上对象存储可以像 HDFS 一样使用但需要充分理解其局限性。现有 Hadoop 应用难以直接迁移仍需额外的开发和适配工作。对于直接使用 HDFS API 编写的代码需要避免重命名操作并减少文件列表操作以适应对象存储的特性。为避免现有 Spark 应用性能下降需考虑使用 Magic Committer但它并非总是有效特别是在不支持 Spark 动态分区覆盖的情况下。此外虽然 Spark 和 Hadoop 社区持续改进对象存储相关问题但更新软件包版本和解决问题仍然面临挑战。使用 S3 兼容的对象存储时还需验证其与 S3 API 的兼容性。03 在 Hadoop 中使用 JuiceFSJuiceFS 是一款分布式文件系统架构由客户端、元数据引擎和数据存储组成。对象存储仅用于存储数据块而文件系统所需的元数据则由数据库管理。需注意 JuiceFS 是与 HDFS 类似的分布式文件系统。因此与直接使用对象存储不同JuiceFS 能完美支持 HDFS API、POSIX API 和 Kubernetes CSI 驱动。为了在速度慢且修改困难的对象存储上实现分布式文件系统JuiceFS 引入了 chunk、slice 和 block 概念。chunk64MB将文件分割为 64MB 单位支持基于偏移的并行处理。slicechunk 内的修改单位写入时创建新 slice 并优先使用最新版本。block默认 4MB实际存储在对象存储中的最小单位通过并行处理缩短上传时间。此外从远程对象存储读取数据较慢JuiceFS 支持多级缓存以此弥补此性能不足。NAVER 内部 AI 平台已使用 JuiceFS。更多关于 JuiceFS 的详细信息及 AI 平台引入过程可参考为 AI 平台引入存储方案 JuiceFS。JuiceFS 支持 Hadoop SDK通过配置 JuiceFS 后用户即可在 Hadoop 环境中使用它。配置 JuiceFS为使 Hadoop 识别 JuiceFS 文件系统需在 core-site.xml 文件中添加以下内容。其中 fs.jfs.impl、fs.AbstractFileSystem.jfs.impl 和 juicefs.meta 是必需的。/* by 01022.hk - online tools website : 01022.hk/zh/tuya.html */ !-- Configure JuiceFS to be available via jfs:// -- property namefs.jfs.impl/name valueio.juicefs.JuiceFileSystem/value /property property namefs.AbstractFileSystem.jfs.impl/name valueio.juicefs.JuiceFS/value /property !-- juicefs meta url -- property namejuicefs.meta/name valueredis://:passwordaddr/value /property !-- In this example, grant access permissions to all users to avoid permission issues. -- property namejuicefs.umask/name value000/value /property !-- Cache up to 100 GiB. -- property namejuicefs.cache-size/name value102400/value /property !-- Cache under the temporary path of YARN containers, so the cache is removed when the container terminates. Since its a shared Hadoop, caching is temporary only during job execution. -- property namejuicefs.cache-dir/name value${env.PWD}/tmp/value /property !-- Prometheus remote write configuration for metrics collection -- property namejuicefs.push-remote-write/name valuehttp://host:port/value /property property namejuicefs.push-remote-write-auth/name valueusername:password/value /property !-- Additionally collect Hadoop user and YARN container ID. For shared Hadoop to distinguish users and applications. -- property namejuicefs.push-labels/name valueuser:${env.USER};container_id:${env.CONTAINER_ID}/value /property以上为单文件系统的默认配置但也可根据需要配置多个文件系统同时使用。更多配置选项可参考“客户端配置”。Hadoop SDKHadoop SDK 的 JAR 文件可以通过下载预编译客户端或自行编译源代码获取。为了简化部署通常可以在所有 Hadoop 节点的 Hadoop 发行版安装路径中预先安装。然而在大规模 Hadoop 集群中这种方法操作繁琐尤其是对于公共 Hadoop 环境它会限制所有用户使用特定版本。大多数 Hadoop 应用支持将所需 JAR 文件部署并添加到 classpath 中用户可根据实际需要选择部署方式。以下是 HDFS CLI、MapReduce 和 Spark 中的具体部署方法。HDFS CLI配置完上述/* by 01022.hk - online tools website : 01022.hk/zh/tuya.html */ core-site.xml文件后需要在HADOOP_CLASSPATH环境变量中设置 Hadoop SDK 文件路径。完成此设置后您可以使用hdfs命令操作hdfs://和jfs://文件系统。$ export HADOOP_CLASSPATH/home/juicefs/juicefs-hadoop-1.2.3.jar $ hdfs dfs -ls hdfs://home/foo Found 6 items ... drwx------ - foo users 0 2022-10-14 20:55 hdfs://home/foo/.Trash drwx------ - foo users 0 2022-01-06 10:18 hdfs://home/foo/dfsio drwx------ - foo users 0 2025-01-22 17:54 hdfs://home/foo/tpcds $ hdfs dfs -ls jfs://default/ 2025-08-25 19:15:43,964 INFO fs.TrashPolicyDefault: Namenode trash configuration: Deletion interval 60 minutes, Emptier interval 60 minutes. Found 8 items ... drwxrwxrwx - 10000 hadoop-admins 4096 2025-06-10 18:06 jfs://default/nyc drwxrwxrwx - 10000 hadoop-admins 4096 2025-05-15 19:42 jfs://default/subdirMapReduceMapReduce 在 Hadoop 的多个节点上并行运行因此所有分配任务的节点都需要部署 JAR 文件。推荐的方法是通过分布式缓存进行部署。使用此方法时任务执行时会自动将mapreduce.application.framework.path中设置的 MapReduce 框架部署到任务节点。以下是mapred-site.xml文件的示例配置mapreduce.application.framework.path指定包含 Hadoop SDK 的 MapReduce 框架的 HDFS 路径。mapreduce.application.classpath配置为包含 Hadoop SDK 的路径。property namemapreduce.application.classpath/name value$PWD/mr-framework/hadoop/share/hadoop/mapreduce/*:$PWD/mr-framework/hadoop/share/hadoop/mapreduce/lib/*:$PWD/mr-framework/hadoop/share/hadoop/common/*:$PWD/mr-framework/hadoop/share/hadoop/common/lib/*:$PWD/mr-framework/hadoop/share/hadoop/yarn/*:$PWD/mr-framework/hadoop/share/hadoop/yarn/lib/*:$PWD/mr-framework/hadoop/share/hadoop/hdfs/*:$PWD/mr-framework/hadoop/share/hadoop/hdfs/lib/*:$PWD/mr-framework/hadoop/share/hadoop/tools/lib/*/value /property property namemapreduce.application.framework.path/name valuehdfs://mapred/framework/hadoop-mapreduce-3.1.2-juicefs-1.2.3.tar.gz#mrframework/value /propertySparkSpark 的基本配置文件是spark-defaults.conf。在该文件中可以替代core-site.xml进行如下设置任意 Hadoop 设置可以通过spark.hadoop.keyvalue形式添加。spark.jars指定要部署到 Spark driver 和 executor并包含在 classpath 中的 JAR 文件。spark.hadoop.fs.jfs.impl io.juicefs.JuiceFileSystem spark.hadoop.fs.AbstractFileSystem.jfs.impl io.juicefs.JuiceFS spark.hadoop.juicefs.meta redis://:passwordaddr spark.hadoop.juicefs.umask 000 spark.hadoop.juicefs.push-remote-write http://host:port spark.hadoop.juicefs.push-remote-write-auth username:password spark.hadoop.juicefs.push-labels user:${env.USER};container_id:${env.CONTAINER_ID} spark.hadoop.juicefs.cache-size 102400 spark.hadoop.juicefs.cache-dir ${env.PWD}/tmp spark.jars hdfs://juicefs/juicefs-hadoop/juicefs-hadoop-1.2.3.jar04 JuiceFS 改进事项JuiceFS 提供多种接口支持跨平台的数据共享。例如在 Hadoop 中使用 MapReduce 或 Spark 处理的数据存储到 JuiceFS 后可以轻松在 Kubernetes 环境中访问和使用这些数据。为使 NAVER 公共 Hadoop 和基于 Kubernetes 的 AI 平台顺畅共享数据需要进行一些改进。已经全部贡献到社区版。支持 all-squash 挂载#5394NAVER 公共 Hadoop 与 LDAP 集成管理用户账户因此 Hadoop 中创建的数据由相应用户的 LDAP UID 和 GID 所有。然而在 Kubernetes 中容器可以使用任意 UID 和 GID 运行这可能导致访问 Hadoop 创建的数据时产生权限问题。为了解决这个问题我们增加了挂载选项--all-squash。该选项使得访问挂载路径时操作不会以当前账户的 UID 和 GID 进行而是使用指定的 UID:GID。因此设置 Hadoop 用户的 LDAP UID 和 GID 后Kubernetes 中的容器可以无权限问题地访问数据。改进 juicefs.users 和 juicefs.group 设置方式#4723如前所述在 Hadoop 集群中执行任务时数据归 Hadoop 用户的 LDAP UID 和 GID 所有。但在 Hadoop 集群外部使用 Hadoop SDK 时数据归任意 UID 和 GID 所有。例如在 Docker 容器中使用 HDFS 命令存储数据时所有者为容器内部账户的 UID 和 GID。为了解决这个问题用户需要通过juicefs.users和juicefs.groups设置指定所需的 UID 和 GID。之前这要求用户编写用户名:UID和组名:GID格式的文件并设置文件路径这个过程非常繁琐。现在我们增加了直接通过配置值来指定 UID 和 GID 的功能简化了操作。支持 subdir#6096在基于 Kubernetes 的 AI 平台中JuiceFS 以动态供应方式使用。创建 PersistentVolumeClaimPVC时会在 JuiceFS 文件系统内生成与该卷对应的子目录。若要在 Hadoop 中共享该 PVC需仅安全地共享该卷对应的目录。然而Hadoop SDK 并不提供类似--subdir的挂载选项无法限制 Hadoop 仅访问 JuiceFS 的特定子路径。为了解决这个问题我们在 Hadoop SDK 中增加了juicefs.subdir设置使用此设置可以限制仅访问指定路径。通过 hdfs 命令查看配额#5937JuiceFS 可以为整个文件系统或特定目录设置配额。在 Kubernetes 中PVC 的spec.resources.requests.storage值将设置为该目录的配额。在 Hadoop 与 PVC 共享时也需要查看配额信息。然而原有的 HDFS 命令hdfs dfs -count -q无法查看 JuiceFS 的配额。为了解决这个问题我们对该功能进行了改进现在可以通过相同的命令查看 JuiceFS 的配额信息。支持 Prometheus remote_write 协议#6295使用 JuiceFS Hadoop SDK 时可以将指标发送到 Pushgateway 和 Graphite。但 Pushgateway 需要定期清理指标且 Graphite 格式独特使用起来较为困难。许多系统支持 Prometheusremote_write协议。为了解决这个问题我们在 JuiceFS 中增加了通过该协议发送指标的功能。通过juicefs.push-remote-write和juicefs.push-remote-write-auth设置用户可以指定 VictoriaMetricsvmagent或 Prometheus。这一功能不仅整合了跨平台数据还能整合监控系统。05 JuiceFS 的优势优势 1通过并行处理和缓存克服对象存储的性能瓶颈JuiceFS 需要通过网络与远程对象存储交换数据块因此在性能上难以超越具有数据本地性优势的 HDFS。然而通过将数据分块并行处理以及缓存已读取数据可以克服这一性能瓶颈。我们通过性能测试验证了 HDFS 和 JuiceFS 在不同场景下的表现。DFSIO使用 10 个 map task针对 100GB 文件测量 HDFS 和 JuiceFS 的顺序数据写入和读取的吞吐量。数值越高性能越好。为适应顺序写入/读取将 JuiceFS 的块大小设为 16MB。写入JuiceFS 的吞吐量是 HDFS 的 1.7 倍。这是因为数据被分割成小块并行上传。读取JuiceFS 的吞吐量是 HDFS 的 0.75 倍。但如果数据已缓存预期性能与 HDFS 相似。TPC-DS使用 Spark SQL 测量对存储在 HDFS 和 JuiceFS 的 100GB 规模表的查询响应时间。数值越低性能越好。JuiceFS 的响应时间是 HDFS 的 1.8 倍这是由于数据本地性差异所致。已缓存的 JuiceFS 表现出与 HDFS 相似的性能。优势 2与 HDFS 完全兼容无需修改现有 Hadoop 应用即可使用NAVER 拥有稳定运营的公共 Hadoop 集群运行着多种服务的 Hadoop 应用。如果仅将不常用的数据存储在对象存储中以降低存储成本可能会出现问题。正如前所述对象存储不是文件系统无法保证现有 Hadoop 应用的性能和运行。为此需要重写代码或检查数据处理引擎是否支持对象存储。此外还需根据存储类型单独运行和管理 Hadoop 应用增加了管理负担。与之相反使用 JuiceFS 可以保持现有 Hadoop 应用不变。用户只需将输入输出路径指定为hdfs://或jfs://即可以相同方式运行应用。HDFS 基于数据本地性保证高性能而对象存储则在低成本和扩展性方面具有优势。两者各有所长难以完全替代需要根据需求选择。使用 JuiceFS 可以在不修改现有 Hadoop 应用的情况下同时利用 HDFS 和对象存储的优势。优势 3支持多种接口可作为跨平台集成存储NAVER 使用多种平台进行服务开发和运营。例如在开发/运营 AI 服务时需要在数据处理平台中清洗数据在 AI 平台中训练模型并通过容器平台提供服务。在 NAVER各个平台提供独立的存储平台内部易于使用但难以访问其他平台的存储。不同平台的存储隔离导致了数据孤岛现象并容易造成数据重复和资源浪费。JuiceFS 不仅支持 HDFS还完美兼容 POSIX 和 Kubernetes CSI 驱动适合作为跨平台的集成存储。通过在多个平台间顺畅使用 JuiceFS 共享数据可大幅提升 AI 服务开发效率实现数据统一管理。06 结语本文探讨了 JuiceFS 在 Hadoop 环境中的使用方法及其优势而在部分业务场景下直接采用 HDFS 或对象存储会是更适配的选择。例如当业务需要依托数据本地性实现高效快速处理时建议将数据存储于 HDFS 中此外针对访问频率较低的数据或采用 Iceberg 等专为对象存储优化的数据格式时直接使用对象存储则更为简便。而在以下场景中JuiceFS 会是更优选择需在 Kubernetes 与 Hadoop 环境之间实现数据共享时希望在不修改现有 Hadoop 应用代码的前提下与 HDFS 并行部署使用时处理存在重复读取行为、可通过缓存显著提升效率的数据作业时业务所用 S3 API 无法被底层 S3 兼容存储良好支持时。本文介绍了在 NAVER 内部本地环境中的应用案例但在 AWS、Google Cloud 等公有云环境中同样适用。希望对有类似困扰的读者有所帮助。