ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

HPC对象存储架构设计与性能调优实战指南

HPC对象存储架构设计与性能调优实战指南 简介面向高性能计算场景的分布式对象存储系统COSS学术论文适合分布式系统研发人员、存储技术研究者及高校相关专业学生作为专业指导与参考文献。论文刊发于《计算机工程》2017年第43卷第8期作者来自江南计算技术研究所针对Lustre等传统分布式文件系统在数据组织效率低、访问语义冗余方面的瓶颈提出数据访问与数据管理分离的简洁语义并采用分布式全局对象组织方式与基于内存的元数据管理方法。实验数据表明大规模并发访问下系统读/写聚合带宽相比Lustre分别提升22.5%和50.4%文件创建与删除性能分别达到2.15倍和5.13倍同时具备拟线性的数据读写与元数据管理扩展能力对大数据、科学计算、机器学习等场景有显著价值。压缩包内为单篇PDF原文共1个文件包体约350KB可直接用于分布式存储、高性能计算方向的参考学习文中对对象存储、存储语义、元数据管理、可扩展性等关键概念均有系统解析适合需要深入理解HPC存储优化方案和开展相关研究的读者。读者可通过原文核对实验数据与架构细节用于论文参考或工程实践。目前已有112人学习/下载。1. HPC 场景为什么需要换一种对象存储从 POSIX 语义到对象语义很多做高性能计算的朋友一听到对象存储第一反应是“那是互联网公司存图片视频用的跟我们的 MPI 程序没关系”。但实际上近五年新建的 HPC 集群里分布式对象存储系统越来越多地出现在数据湖和中间存储层它有近乎无限的水平扩展能力、S3 协议兼容性好、不需要专门的元数据服务器做全局锁成本还比并行文件系统低一截。这篇笔记要拆的就是这类系统在 HPC 环境里怎么设计、怎么搭、怎么压测以及哪些坑会让人翻车。适合正在调研存储选型、或者想把已有数据从 Lustre/GPFS 迁到对象存储的工程师。2. 对象存储如何扛住高并发聚合带宽架构选型与关键组件2.1 元数据与数据分离HPC 对象存储的第一原则传统并行文件系统崩溃多半是元数据服务器先扛不住。Lustre 的 MDS、GPFS 的 NSD 元数据节点在几万个核同时做 stat、open、close 的时候锁竞争和日志写盘会迅速拖垮整个集群。对象存储几乎都采用元数据与数据分离的架构数据节点只负责存对象元数据节点维护“桶-对象”的映射。这个设计的好处是HPC 工作负载里的典型数据流——一个进程写一个文件几千个进程同时写几千个文件——在对象存储里变成了几千个独立的 PUT 请求元数据操作被削减到最少。对于 HPC 场景元数据节点建议用高主频 CPU NVMe 盘因为每个 PUT 都要先查桶配置和 ACL延迟多几十微秒都会影响小文件写带宽。而数据节点则要追求带宽密度万兆网卡起步PCIe 4.0 / 5.0 的 NVMe SSD 做写缓存机械盘做冷数据分层这样聚合带宽才能线性往上加。这里有一条血泪经验不要把元数据节点和数据节点混布。混布时一次数据节点的慢盘抖动会把元数据请求的队列阻塞住表现为整个集群 OPS 瞬间掉到零查日志只会看到一堆超时。2.2 纠删码还是多副本带宽与容错怎么权衡这个决定影响整个系统的可用容量和写入带宽。多副本比如三副本实现简单但容量利用率只有 1/3且每写一份数据要发三次网络请求带宽浪费明显。纠删码EC用 NM 校验块常见的是 42 或 82可用容量能到 66%~80%但对 CPU 有开销写入路径会把编码工作分摊到客户端或数据节点。HPC 场景我的建议是分两级中间存储层用 EC 82 或 63因为 HPC 计算产出的中间结果一旦丢失可以重算不需要三副本那种激进容错最终归档结果用三副本或两副本因为从模拟里出来的结论数据往往不可再生。EC 编码参数里条带块大小stripe size建议对齐对象大小否则小对象会触发读改写read-modify-write性能直接腰斩。具体对齐逻辑是对象大小 stripe size 时写入只需编码一次对象大小跨多个 stripe 时每跨一个条带都要做一次部分块更新的读改写开销会成倍增加。2.3 客户端访问路径S3 网关还是原生 API面向 HPC 的对象存储一般提供两种访问方式一种是兼容 S3 的 RESTful API通过网关进程转发请求另一种是原生客户端库直接用私有协议和存储节点通信。S3 网关的好处是生态兼容大数据组件、AI 框架都原生支持 S3坏处是每多一层转发就多一跳延迟网关容易成为瓶颈。在 HPC 里我一般会用原生 API 做高带宽数据传输用 S3 协议做业务数据接入。如果团队已经基于 POSIX 开发了大量代码还可以挂一层 FUSE 适配器但对性能别抱太高期望——对象存储本来就是为“最终一致性大文件顺序写”设计的FUSE 层会把随机读问题全部暴露出来。常见做法是把 FUSE 只挂载在登录节点用于目录浏览和文件拷贝计算节点直接用 S3 客户端库绕开 FUSE。这里还要注意S3 网关的并发连接数默认限制往往偏低HPC 里有几百个客户端同时启动时网关容易拒连必须提前把最大连接数和线程池调大。3. 搭一套面向 HPC 的最小对象存储从部署到压测3.1 部署规划节点角色与网络拓扑我以一个四节点的最小集群为例一个元数据节点三个数据节点。操作系统是 Ubuntu 22.04数据节点各挂 4 块 NVMe SSD 和 20 块 HDD网卡是 100G RoCE。注意HPC 场景下网络是命脉建议控制节点和数据节点都用双网卡一张跑业务流量一张跑内部复制和心跳避免集群内数据同步把对外带宽打满。我们以开源的 Ceph RGW 作为例子因为它对 EC、多站点、S3 API 的支持比较完整。部署时先配置好各节点的主机名和免密登录然后在管理节点上安装 cephadm。最小化部署的集群规划如下表节点角色配置ceph-mon1Monitor RGW32C / 256G / 2xNVMeceph-osd1OSD32C / 128G / 4xNVMe 20xHDDceph-osd2OSD同上ceph-osd3OSD同上第一件要做的事是把内核的read_ahead调小因为 HDD 做对象存储底层时内核预读会打乱 RGW 自己的顺序读逻辑。我一般会把它设为 256KB# 在三个 OSD 节点上执行 echo 256 /sys/block/sdb/queue/read_ahead_kb这里把sdb换成你实际的 HDD 盘符。内核预读值设置过大时RGW 的读请求被预读放大底层盘多读了很多无用数据吞吐反而下降。设置完可以用blockdev --getra验证一下。3.2 基础配置存储池、桶与访问密钥Ceph RGW 需要为数据、元数据、索引分别创建存储池。对于 HPC 使用建议把数据池的 size 设为 2两份副本元数据池和索引池使用三副本因为对象查找和桶索引的可靠性直接影响可用性# 创建存储池 ceph osd pool create hpc.data 128 replicated ceph osd pool create hpc.meta 64 replicated ceph osd pool create hpc.index 64 replicated # 设置副本数 ceph osd pool set hpc.data size 2 ceph osd pool set hpc.meta size 3 ceph osd pool set hpc.index size 3 # 初始化 RGW 实例 radosgw-admin realm create --rgw-realmhpc radosgw-admin period update --commit radosgw-admin user create --uidhpcuser --display-nameHPC User --access-keyAKIAHPC123 --secret-keySKHPC456789这里的关键参数是副本数数据池用 2 副本已经能容忍单盘故障而 HPC 中间结果不需要三副本但元数据和索引池必须三副本否则ceph-objectstat这类操作会卡在索引读上。--access-key和--secret-key是你自己定义的生产环境建议用随机字符串避免被人猜到。创建完用户后还需要创建一个桶来存放计算输出# 配置 s3cmd 连接 RGW cat ~/.s3cfg EOF [default] access_key AKIAHPC123 secret_key SKHPC456789 host_base rgw.local:8080 host_bucket rgw.local:8080 use_https False EOF # 创建桶 s3cmd mb s3://hpc-output注意host_bucket要设置成网关地址加端口否则 s3cmd 会尝试访问bucket.rgw.local这种不存在的子域。Ceph RGW 默认监听 8080 端口如果你改了端口要连同rgw_bucket_default_quota一起检查。HPC 场景下我给桶默认设置一个容量告警阈值比如 500TB防止失控把盘写满。3.3 压测用 cosbench 验证读写带宽与 OPS部署完不能直接上生产先用 COSBench 压一遍。COSBench 是标准对象存储压测工具支持 S3 协议。我们要测两个典型场景大文件顺序写模拟检查点输出和小文件随机读模拟后处理读取。先准备一个写入配置文件?xml version1.0 encodingUTF-8 ? workload namehpc-write descriptionHPC large file write storage types3 configaccesskeyAKIAHPC123;secretkeySKHPC456789;endpointhttp://rgw.local:8080 / workflow workstage nameinit workers8 operation typesetup ratio10 configcprefixhpcwrite;containersr1 / operation typeprepare ratio90 configcprefixhpcwrite;containersr1;objectso1_1;objectsize64MB / /workstage workstage namemain workers64 operation typewrite ratio100 configcprefixhpcwrite;containersr1;objectso1_1;objectsize64MB / /workstage workstage namecleanup workers8 operation typecleanup ratio100 configcprefixhpcwrite;containersr1 / /workstage /workflow /workload运行方式./cosbench.sh start -W ./hpc-write.xml -T 120这个配置里workers64是关键太少压不满网络太多会把网关线程池打爆。64 是四节点集群常见的起点之后按实测结果逐步调。objectsize64MB模拟的是 HPC 检查点文件大小如果你们的检查点是 GB 级要改成 1GB 或 2GB。压测结束后看生成的 CSV 报告里的Avg-ResTime和Throughput两列写带宽优先看 Throughput网络延迟看 ResTime。4. HPC 对象存储的 3 个必调参数小文件聚合、并发数与网络缓冲区4.1 小文件聚合避免 OPS 瓶颈HPC 应用里最常见的分崩离析就是明明存储侧顺带宽很高一跑起大量小文件读写的作业就全线拉胯。原因很简单对象存储每个请求都有固定开销元数据查询、鉴权、网络 RTT这些小开销在 200KB 大小的文件上没有被数据量摊薄OPS 就成了瓶颈。解决办法是把小文件聚合成大对象业内叫 small file aggregation。常见的做法是引入一个聚合层在写入端把同一目录下的小文件按 64MB 打包成一个对象再在对象内部记录每个小文件的 offset 和 size。这个打包逻辑可以用客户端脚本实现。我一般会用 Python 写一个轻量聚合器# small_file_aggregator.py import os import boto3 from pathlib import Path AGG_BUCKET hpc-agg CHUNK_SIZE 64 * 1024 * 1024 def aggregate(base_dir): s3 boto3.client(s3, endpoint_urlhttp://rgw.local:8080) chunk b manifest [] chunk_id 0 for root, _, files in os.walk(base_dir): for fname in files: fpath Path(root) / fname fsize fpath.stat().st_size if fsize CHUNK_SIZE: # 大文件单独上传 s3.upload_file(str(fpath), AGG_BUCKET, fname) continue if len(chunk) fsize CHUNK_SIZE: key fagg/pre/{chunk_id}.dat s3.put_object(BucketAGG_BUCKET, Keykey, Bodychunk) manifest.append({chunk_key: key, files: [...]}) chunk b chunk_id 1 chunk fpath.read_bytes() manifest[-1][files].append({name: fname, offset: ...})这段代码的逻辑是遍历目录把小文件依次拼到内存缓冲区每满 64MB 就上传一个对象同时记录清单。里面有两点很关键CHUNK_SIZE要和 RGW 的 stripe size 对齐否则聚合对象会触发多次读改写manifest 清单要单独上传并加上x-amz-meta-自定义元数据方便读取端快速定位。聚合之后小文件读写从 OPS 型变成带宽型整体吞吐可以提升一个数量级。4.2 并发数与客户端线程模型对象存储不像 POSIX 文件系统那样靠内核维护文件描述符它的并发模型是“客户端开多少线程就能发多少请求”。很多 HPC 管理员照着并行文件系统的习惯只开 4 个进程做并行读写结果带宽死活上不去。实际上S3 客户端通常需要至少 16 到 32 个并发连接才能打满万兆网络。对于 C 程序常见做法是用 AWS SDK 的TransferManager它内部维护一个线程池默认并发数偏保守。我一般会显式调参// config_hpc_s3.cpp Aws::Transfer::TransferManagerConfiguration config; config.transferBufferSize 16 * 1024 * 1024; // 16MB 缓冲 config.threadPoolSize 32; // 并发线程数 config.executor Aws::MakeSharedAws::Utils::Threading::PooledThreadExecutor( hpc-executor, 32);这里threadPoolSize是每客户端占用线程数配合 32 个客户端进程就能发出 1024 个并发请求。需要注意的是Ceph RGW 的rgw_thread_pool_size默认只有 1024如果总并发超过这个数网关会拒绝请求表现为客户端收到慢请求或连接重置。生产环境建议把 RGW 的线程池和rgw_max_chunk_size一起调我通常设置为 2048 和 4MB。4.3 RDMA vs TCP网络缓冲区与 MTU 调整高性能计算集群普遍有 InfiniBand 或 RoCE但对象存储的客户端软件未必都支持 RDMA。如果走 TCP 跑 100G 网默认的内核 TCP 缓冲区根本不够用会导致吞吐只有带宽的一半。这里有两种路线一是直接用支持 RDMA 的原生对象存储协议比如 Ceph 的ms_public_v2接口可以配置rdma传输二是坚持用 TCP但把读写缓冲区调到最大。以 TCP 路线为例需要在所有客户端和存储节点上调整sysctl -w net.core.rmem_max134217728 sysctl -w net.core.wmem_max134217728 sysctl -w net.ipv4.tcp_rmem4096 87380 134217728 sysctl -w net.ipv4.tcp_wmem4096 65536 134217728调完 TCP 缓冲区还要注意 MTURoCE 网卡的标准 MTU 是 4096而 TCP 默认走 1500。如果网卡和交换机都支持 jumbo frame把 MTU 统一设成 9000高带宽下的 CPU 开销会明显下降。这里有个玄学MTU 不匹配时存储节点能 ping 通但大包传输速率上不去。检查的时候不要只看 ping要发多线程大包测试比如iperf3 -c rgw_ip -t 60 -P 16 -l 128K如果吞吐随-l从 128K 降到 4K 而暴跌多半是 MTU 问题或 RDMA 门限设置问题。另外把 RGW 的rgw_socket_options里面的 TCP_NODELAY 打开可以避免小包因为 Nagle 算法延迟堆积。5. 从并行文件系统迁移到对象存储数据路径与接口适配的避坑指南5.1 现象应用起不来报错“No such file or directory”迁移后很多 JOB 在启动阶段直接报错找不到目录原因不是对象存储侧没数据而是应用还在用 POSIX 的路径访问方式比如fopen(/hpc/data/foo.nc, r)。对象存储没有层级目录只有桶和扁平对象名S3 客户端库虽然允许叫foo/bar.txt但不会真正创建目录。解决思路是在客户端加一层路径映射把 POSIX 路径转换为对象名。常见做法是写一个 shim 库拦截open/read/write把从/hpc/data/开头的路径映射到s3://bucket/data/。我经历过最稳的是直接用系统提供的 FUSE 挂载但在计算节点上不推荐FUSE 缓存会占用内存且并发高时内核态和用户态切换频繁。替代方案是用 S3 原生 API 改写文件操作把open变成GetObject把write变成PutObject。如果是 MPI-IO可以在 ROMIO 层接入对象存储的 ADIO 驱动这样应用代码不用改但要确保驱动版本和客户端库一致。5.2 现象小文件读写比原来慢一个数量级迁到对象存储后最伤感情的是后处理阶段的小文件读取。在 Lustre 上毫秒读一个文件换成对象存储后变成几百毫秒。这个坑的本质是对象存储的最小读单元是对象哪怕你只要 4KB 数据也要完整拉取对象的元数据、校验 ACL、再到数据节点读整个对象块。解决有几个手段最有效的是前面提到的小文件聚合其次是开启 RGW 的缓存和预取rgw_cache_enabledtrue配合rgw_cache_lru_size调大能缓存热点对象的元数据。第三把客户端端的 S3 连接池打开避免每个请求重新建连。我用一个实际案例说明一个 5000 个小文件的后处理任务在 Lustre 上耗时 3 分钟直接挂 S3 跑要 45 分钟做了聚合和连接池优化后压到 6 分钟虽然没追平但已经能接受。5.3 现象检查点恢复时数据不一致HPC 计算作业常常依赖检查点机制正常结束后写一轮 checkpoint崩溃后读回来。对象存储的最终一致性模型会把这里变成深坑。具体表现是作业写完了当前 checkpoint打印日志说“写完成”但下次启动读取时发现最后一个对象不存在。原因在于对象存储的写操作从网关返回成功不代表数据已经落盘。对于 HPC 场景必须显式等待一致性确认。RGW 可以设置桶的一致性级别但更稳的做法是在写检查点后额外写一个done标记对象# 读取端只认 done 标记 s3.put_object(Buckethpc-output, Keyjob_id /checkpoint/done, Bodyok)读取端只有在看到done对象后才去拉取前面的分块。如果使用多副本还可以对关键对象开启x-amz-object-lock-mode的合规保护至少能避免误删。还有一个隐蔽问题RGW 默认的 multipart 上传每个分片完成后并不保证全局可见要等 complete-multipart-upload 返回成功才安全。用客户端库时一定要调用complete()而不是直接close()。5.4 现象达到一定规模后性能断崖集群从 10 个节点扩到 50 个后吞吐不升反降出现明显的断崖。这通常不是节点性能问题而是数据分布不均。对象存储的 CRUSH 映射或哈希分桶在节点少时很均匀一旦扩容大量数据需要重新映射后台数据均衡会占用带宽和 CPU导致前台性能下降。解决方法是提前规划容量水位一般保持 OSD 使用率不超过 70%给重均衡留出余量。另外扩容时不要所有节点一次加入分批加入每批做一轮 backfill 计时观察。还要检查是否有热点对象被打到一个 OSD 上——HPC 里经常有几个超大的 checkpoint 文件它们被拆成多个对象后恰好落在同一个 OSD造成单盘瓶颈。这时可以牺牲一点前缀空间在对象 key 前面加一段随机前缀打散。6. 让对象存储在 HPC 生产环境真正落地验证手段与性能回放到最后一步光看压测平均吞吐是不够的。HPC 生产环境关心的是最坏场景某个节点挂了、某个盘慢到 100ms、网络发生微突发系统能不能扛住。我习惯做三轮验证。第一轮是故障注入随机拔掉一个 OSD 节点的数据网线观察 RGW 是否还能继续读写未受影响的对象恢复时间是多少。第二轮是性能回放从监控里取一个真实作业的 IO 访问序列用cosbench或s3curl按相同的时间戳和对象大小回放对比迁移前后的累计耗时。第三轮是长稳测试跑 7 天持续写入每 24 小时记录一次 OSD 的 read_ops、write_ops 和bluestore的延迟分布看是否有内存泄漏或句柄泄漏。具体有个技巧值得分享利用 RGW 的ops log做性能回放。开启rgw_enable_ops_logtrue后网关会记录每一个请求的对象名、大小、耗时然后用它生成回放脚本。这个做法的价值在于它抓的是真实业务模式而不是我们拍脑袋想出来的压测模型。验证通过后还需要设一个监控告警阈值。我一般盯四个指标RGW 的平均响应时间超过 1 秒、OSD 队列深度超过 2048、客户端吞吐低于基线 80% 且持续 5 分钟、元数据节点的 CPU 超过 85%。这四个指标能覆盖绝大部分生产事故。另外别忘了一件事给所有数据开启回收站生命周期策略防止误删检查点HPC 的检查点文件往往没有历史版本删了真的就没了。这套方案跑了半年后我最后悔的是没有更早做小文件聚合和故障注入。前两个星期光顾着调带宽参数结果一次数据节点掉线就暴露出网关线程池太小的问题。希望这篇笔记里的踩坑记录能帮到你让你不用重新趟一遍。本文还有配套的精品资源点击获取
返回列表