burst buffer是超算中一种作业加速技术,主要解决全球气候模拟预测建模、流体力学分析、磁性融合、天体物理学、生物分子模拟中浪涌型I/O的情况,burst buffer作为前端计算和后端存储之间的缓冲区,它弥合了计算节点的处理速度与存储系统的I/O带宽之间的性能差距。 burst buffer通常由高性能存储设备阵列构建,例如 NVRAM 和 SSD。 它可以提供比后端存储系统高一到两个数量级的 I/O 带宽。
1.burst buffer简介
1.1 burst buffer应对的技术场景
许多科学领域越来越多地使用高性能计算(HPC)来处理和分析大量的实验数据,存储也面临着更复杂的模式,这些模式包括许多元数据操作、小型I/O请求或随机的文件I/O;今天的HPC环境中的存储系统必须应对新的访问模式,而通用的并行文件系统虽然优化了对大文件的顺序共享访问,但是面对浪涌型I/O仍然捉襟见肘,burst buffer技术应运而生。burst buffer一般是一个单独的文件系统,应用程序可以用他来存储临时数据,它聚集了计算节点的高速本地存储,或使用专用的SSD集群,提供高于后端带宽的峰值从而实现对作业应用的加速。
1.2 burst buffer为何使用nvme作为缓冲区
在计算机中存储器一般使用CPU寄存器-cache缓存-内存-硬盘四级结构,容量越大其速度也就相对越低,但同时价格也会越便宜,机械硬盘具有数据存储的安全性,容量可以做到很大,而固态硬盘由于其有限的读写寿命作为后端存储必然不能保证用户数据的安全性,这也是当前机械硬盘作为大多数集群后端存储的原因。然而固态硬盘的随机读写性能有着天然的优势,能够大大加速文件的写入和读取过程;而内存虽然速度更快,但是由于容量限制和价格因素不是作为burst buffer的首选,因此现在大部分集群都是使用固态硬盘作为burst buffer缓冲区。
2. 超算中一些常用的burst buffer技术
burst buffer的重要性不言而喻,在世界顶级超算中多数都有部署burst buffer,如Frontier、summit、NERSC、BSC、CCS都是使用NVME作为突发缓冲区进行加速,burst buffer技术分类可以有多种,这里根据SSD使用类型归类。
①专用的SSD(NVME)集群,提供独立的文件系统。如克雷公司的DATAWARP,计算节点和burst buffer之间的数据移动需要通过网络,这种方式方便了burst buffer服务的独立开发、部署和维护。
②聚合计算节点的SSD(NVME)硬盘,提供burst buffer缓冲区。如spectral、GekkoFS、burstFS等等。这种架构随着计算节点数量增加,带宽线性增加,具有很强的可伸缩性。
Butst buffer类型 | 磁盘类型 | 文件系统 | POSIX支持性 | 典型超算或机构 |
cray DATAWARP | 专用SSD集群 | 分布式 | 支持 | NERSC |
Spectral | 计算节点本地NVME盘 | 非分布式 | 不支持 | summit |
GekkoFS | 计算节点本地NVME盘 | 分布式 | 支持 | BSC |
BurstFS | 计算节点本地NVME盘+DRAM | 分布式 | 支持 | Florida State University、LLNL |
Gfarm/BB | 计算节点本地NVME盘 | 分布式 | 支持 | Center for Computational Sciences |
下面我们对这些常见的burst buffer技术进行一一介绍。
2.1 cray DATAWARP
DATAWARP方案是使用一批专用的计算节点构成burst buffer缓冲区,当作业被提交时,作业所在计算节点会创建DWFS(并行文件系统),之后将后端存储中作业所用到的数据移动到DWFS中。示意图如下:
当作业运行时,作业将把DWFS进行挂载,应用程序通过标准的POSIX协议与DWFS进行交互。
完成作业过程:将作业数据移动到后端存储,销毁和作业相关数据池。
随着DATAWARP的SSD节点逐渐增加,burst buffer的写入能力逐渐增加。具体数据见下图。
2.2 spectral
Spectral是一个可移植的、透明的中间件库,可以在集群上使用节点本地的burst buffers(由nvme硬盘组成)来加速应用输出。它用于将文件从节点本地的NVMe盘传输回并行文件系统,Spectral在每个预留节点的隔离核心上运行,所以它不占用资源,并且基于一些参数,用户可以复制到后端存储的指定文件夹。spectrald使用了LD_PRELOAD机制,LD_PRELOAD是个环境变量,用于动态库的加载,其加载顺序为:
LD_PRELOAD>LD_LIBRARY_PATH>/etc/ld.so.cache>/lib>/usr/lib
在启用spectral后,首先会对open函数进行拦截,将open函数打开的文件指针由后端存储重定向到burst buffer所在文件夹所在位置,之后write操作都是向burst buffer中写数据,此举能够大大加速文件的写操作。当触发close操作后,本次作业的加速过程宣告完成,clsoe经过重定向后会告知spectrald守护进程接手后续burst buffer数据向后端存储移动的操作,整个操作的流程图如下:
spectral随着节点数量的递增,在标准的IOR测试中,读写能力呈现明显的线性关系。
但是当前spectral是不完善的,如仅对写功能进行支持,且各节点的NVME存储空间相互独立,没有形成一个统一的文件系统,写入文件时不能多对一。而GekkoFS对这些问题有了进一步改善。
2.3 GekkoFS
GekkoFS为用户提供一个临时的文件系统,这个文件系统聚合多个计算节点本地的nvme存储形成一个统一的命名空间,GekkoFS不提供复杂的锁机制,对于重叠的文件区域应该由应用程序本身确保没有冲突。GekkoFS的架构由两个主要部分组成:一个客户端库和一个服务器进程。如果想要使用GekkoFS必须在客户端安装GekkoFS的插件库,客户端插件库会拦截所有的文件系统操作,并在有必要的时候转发给GekkoFS守护进程。
GekkoFS客户端由三个部分组成:
1) 一个拦截接口,用于捕捉应用对GekkoFS的相关调用
2)一个文件映射,管理开放文件和目录的文件描述符,独立于内核。
3)一个基于RPC的通信层,将文件系统请求转发给本地/远程的文件系统。
对于文件系统操作被GekkoFS拦截后,会通过RPC消息转发到一些节点的GekkoFS守护进程,换句话说,GekkoFS使用伪随机分布将数据和元数据分散到所有节点上,每个客户端都能独立处理涉及文件系统操作的节点。为了实现大文件的均衡数据分配,数据请求会在分配前被分割成同等大小的块到组成文件系统计算节点。如果底层网络结构协议支持,客户端可以通过RDMA直接进行访问。
GekkoFS守护进程也是由三部分组成:
1) 一个用于存储元数据的键值存储。
2) 一个I/O持久层,用于从/向底层本地存储系统读/写数据。
3) 一个基于RPC的通信层,接受本地和远程连接来处理文件系统操作。
对于通信层,我们利用了Mercury RPC框架, Mercury是通过Margo库间接连接的,目的是提供一个简单的多线程执行模型。
和spectral类似,在读写情况下,GekkoFS 显示出接近线性的可扩展性,其可用带宽和存储能力随着节点数量的增加而增加。
2.4 Gfarm/BB
Gfarm/BB是一个利用节点本地存储系统的burst buffer文件系统,该临时文件系统仅在作业运行期间有效,在作业执行之前按需构建。为了提高读写性能,它利用了文件描述符传递和远程直接内存访问 (RDMA)。下图是Gfarm的架构图。
它由元数据服务器(gfmds)、文件系统服务器(gfsds)和客户端(应用程序)组成。 元数据服务器由一个主 gfmd、多个同步从属 gfmd 和多个异步从属 gfmds 组成,用于实时故障转移和灾难恢复。 每个 gfmd 在内存中管理最新的文件系统元数据,并将更新保存到一个日志文件中,该日志文件将异步反映到后端 PostgreSQL 数据库。更新也被传输到从属 gfmds。 主 gfmd 等待来自同步从属 gfmds 的响应,但不等待来自异步 gfmds 的响应。 gfsd 是运行在每个计算节点上的文件服务器,用于从远程节点访问节点本地存储。 在每个计算节点上,Gfarm 文件系统由 gfarm2fs 挂载用于 POSIX及 Gfarm API 访问。应用的读写操作通过POSIX及 Gfarm API 接入Gfarm/BB,通过libgfarm调用I/O库函数使用gfsds。
读写带宽和节点数量之间的关系如下图所示。
2.5 burstFS
burstFS是一个分布式的文件存储系统。下图是burstFS的设计架构;
BurstFS建立在CRUISE之上,CRUISE是一个在支持检查点重新启动工作负载的文件系统。CRUISE会拦截应用程序的POSIX函数调用,并同时利用DRAM和SSD存储,将每个进程的数据写入本地存储,这种方式表明CRUISE有强大的可扩展性。CRUISE的模式为N-N写,其中N个进程各自写一个单独的文件,而对于N-1模式写,即N个进程写到一个共享文件,效率较低。当前所面临的主要的挑战是索引以及巨大数量的聚合文件都会导致产生大量的元数据; BurstFS将这种元数据记录到分布式键值存储中;此外,允许进程通过RDMA从远程节点检索数据。由于在HPC系统中BurstFS的寿命与单个作业相关。在每个作业结束时,所有的数据都会被清理掉。
从下图可以看到,对于N-1模式而言,BurstFS对于CRUISE有较大的提升,且随着节点的增加BurstFS的带宽随着节点逐渐增强。
3. slurm与burst buffer的集成
在slurm21版本中集成了对burst buffer插件的支持,包含两种分别是cray的datawarp和通用插件lua,cray需要特定硬件支持,上面已经介绍过其基本原理,这里主要介绍lua插件。lua插件提供一下多个可供二次开发的函数,分别处于作业的不同阶段运行,大部分的lua函数是由slurm中的slurmscriptd守护进程在管理节点进行调用。slurmscriptd调用流程随slurmctld主进程启动,具体流程图如下所示:
在该流程中主要对资源池进行初始化
对于lua其他函数的调用流程如下表所示:
burst_buffer.lua调用时刻表(基于slurm21.08)
函数名称 | 调用时段 | 可以获取变量 |
slurm_bb_pools | slurmctld启动时,burst buffer插件初始化时调用 | |
slurm_bb_job_process | 作业提交时,在进入select_nodes之前 | job_script脚本内容(spool目录下) |
slurm_bb_setup | 当作业pending时,此时在select_nodes函数中,尚未分配nodelist | job_id、job_script |
slurm_bb_data_in | 在调用slurm_bb_setup之后 | job_id、job_script |
slurm_bb_real_size | 在调用slurm_bb_data_in之后 | job_id |
slurm_bb_paths | 作业处于“running+configuring”状态,此时在select_nodes函数中,可重新请求burst buffer资源。此时尚未分配计算节点列表(即将分配)。 | job id、job script、job script的路径相同,path file为可设置的环境变量 |
slurm_bb_pre_run | 作业处于scheduled后,作业running前,此时在select_nodes函数中,计算节点分配完成后立即调用本函数。 | job_id、job_script |
slurm_bb_post_run | 在作业完成后调用,作业处于stage out,当slurmctld收到REQUEST_COMPLETE_JOB_ALLOCATION执行。 | job_id、job_script |
slurm_bb_data_out | 调用slurm_bb_post_run后 | job_id、job_script |
slurm_bb_job_teardown | 作业处于completes or cancelled | job_id、job_script |
slurm_bb_get_status | 运行“scontrol show bbstat” |
这些lua函数对应于slurm中burst_buffer.c通用函数
static const char *req_fxns[] = {
"slurm_bb_job_process",
"slurm_bb_pools",
"slurm_bb_job_teardown",
"slurm_bb_setup",
"slurm_bb_data_in",
"slurm_bb_real_size",
"slurm_bb_paths",
"slurm_bb_pre_run",
"slurm_bb_post_run",
"slurm_bb_data_out",
"slurm_bb_get_status",
NULL
};
这些函数可以分成slurmctld和slurmscriptd两种进程调用,一般来说slurmctld调用的函数需要快速返回值,不能在函数中消耗太多时间,而slurmscriptd进程调用的函数则没有此要求。下面我们对这些函数流程进行一一梳理。
3.1 slurm_bb_pools
bb_p_load_state调用burst_buffer.lua脚本中的slurm_bb_pools(),获取资源池的容量,该函数在slurmctld初始化时调用。调用函数流程关系如下所示(由slurmctld进程调用)。slurmctld守护进程启动的过程中会创建一个关于burst buffer的新线程_bb_agent,该线程主要用来定期更新资源池的容量。
3.2 slurm_bb_job_process
bb_p_job_validate2调用burst_buffer.lua脚本中的slurm_bb_job_process。该函数在作业提交时调用(在建立作业ID和创建脚本文件后执行),对申请burst buffer作业资源进行验证,(由slurmctld进程调用)。
3.3 slurm_bb_setup
slurm_bb_setup由bb_p_job_test_stage_in调用的函数触发,在select_nodes函数中检查完qos、分区的可用性后。slurmctld向slurmscritd发送SLURMSCRIPTD_REQUEST_RUN_BB_LUA类型信息,并包含slurm_bb_setup函数名;slurmscritd中调用_run_bb_script执行burst_buffer.lua中slurm_bb_setup。函数在PD过程中调用(由slurmscriptd进程调用)。流程图如下:
3.4 slurm_bb_data_in
slurm_bb_data_in紧接着slurm_bb_setup函数运行,此时作业仍处于PD状态,都在static void *_start_stage_in(void *x)函数中,此时可以将作业所需的数据移动到指定位置(由slurmscriptd进程调用)。
3.5 slurm_bb_real_size
slurm_bb_real_size紧跟着static void *_start_stage_in(void *x)函数中的slurm_bb_data_in调用,携带发送信息op = "slurm_bb_real_size";(由slurmscriptd进程调用)。
3.6 slurm_bb_paths
slurm_bb_paths对应bb_p_job_begin,请求burst buffer资源。此时尚未分配计算节点列表(即将分配),其中该函数可以传入path_file,path_file指定的文件是一个空文件。如果将环境变量写入path_file,则将这些环境变量添加到作业的环境中。仍在select_nodes函数中(由slurmctld进程调用)。
3.7 slurm_bb_pre_run
slurm_bb_pre_run由slurm_bb_paths所在函数调用新线程_start_pre_run。在_start_pre_run中等待计算节点分配完成。然后向slrumscriptd中发送消息,调用slurm_bb_pre_run函数(由slurmscriptd进程调用)。
3.8 slurm_bb_post_run
slurm_bb_post_run由bb_p_job_start_stage_out函数调用。在作业完成后调用,当slurmctld收到REQUEST_COMPLETE_JOB_ALLOCATION执行_slurm_rpc_complete_job_allocation函数(由slurmscriptd进程调用)。
3.9 slurm_bb_data_out
slurm_bb_data_out紧跟slurm_bb_post_run后调用,一般作业数据输出文件移动到后端存储可以调用此函数(由slurmscriptd进程调用),流程图如下。
3.10 slurm_bb_job_teardown
slurm_bb_job_teardown在完成slurm_bb_data_out后,_queue_teardown
(stage_out_args->job_id,stage_out_args->uid, false);启用新线程_start_teardow调用slurm_bb_job_teardown函数(由slurmscriptd进程调用),可用于清理环境变量或校准等操作。
3.11 slurm_bb_get_status
由scontrol show bbstat触发。slurmctld接收到REQUEST_BURST_BUFFER_STATUS类型的RPC后(由slurmscriptd进程调用)。
4.小结
通过介绍常见的burst buffer技术以及slurm中burst buffer插件可知,目前slurm已经预留好相当多的函数(lua)供我们自定义开发,burst buffer本身就是数据在作业不同生命周期数据的移动,我们只要在slurm中lua函数中契合所采用的burst buffer的技术特点,就能实现作业在运算过程中加速或者从检查点数据恢复。
Slurm调度系统是当前调度系统中少有的开源且成熟的调度系统,但是仍然有少量bug的存在,且不能完全覆盖所有的用户场景,如果您有问题请加入社区讨论。社区以Slurm为切入口讨论HPC相关问题,致力于守卫中国HPC集群稳定运行,推广国产调度器助力中国HPC进步。欢迎私信。