简介:阿里生产集群采集的 clusterdata 数据集,源自阿里巴巴集群追踪计划,主要面向集群管理、调度算法设计与数据中心工作负载特征研究人群。数据包含 cluster-trace-v2017(约1300台机器、12小时记录)与 cluster-trace-v2018(约4000台机器、8天记录,含批处理作业DAG信息),并涉及GPU v2020相关内容,覆盖在线服务与批处理任务混部场景,适合用于验证调度策略、分析混部负载特征和开展教学实验。压缩包共32个文件,大小约16.22MB;以 Markdown 文档、CSV字段定义、Python脚本、Jupyter Notebook 分析示例为主,并配有大量 PNG/JPG 图表、许可证与 SHA256 校验文件,便于快速理解数据格式并核对完整性。已有1161人学习下载,研究者和学生可从真实生产轨迹中提取任务、机器、DAG 等信息,结合附带脚本复现论文实验,具有较强的参考价值。
1. 阿里clusterdata是什么:一段被采集下来的真实生产集群运行史
拿到一份生产集群的数据,做集群管理研究的人第一反应不是画负载曲线,而是先确认:这套数据能不能当调度策略的输入,能不能复现出真实线上的压力。阿里 clusterdata 正是这样一份被广泛引用的公开生产集群数据,它把一批真实服务器的资源状态、容器运行记录、任务调度记录,按固定时间粒度连续采集成 trace,专门服务集群管理和调度研究。它的核心价值是“真实”:机器规格、cpu 波动、内存紧张、任务优先级这些在模拟器里很难捏出来的细节,数据里都有原始痕迹。用它做负载重放、策略对比和容量分析,比自建一套几十台机器的环境更接近线上,也更容易横向对比。下面按“数据组成、文件解析、实验搭建、常见坑”的顺序,把完整路径串一遍。
2. clusterdata数据组织:机器、容器、任务三层对应关系与时间戳口径
2.1 三层元数据:机器、容器、任务各记录什么
解开 clusterdata 压缩包,通常看到两类文件:一类是 meta 结尾的元数据,描述静态配置;一类是 usage 结尾的使用记录,描述动态运行。meta 文件里按层级出现机器、容器、任务三张表,理解它们的关系是后续一切统计的前提。
机器层是最简单的一层,常见字段是机器 ID、CPU 总规格、内存总规格以及机器类型。机器 ID 建议加载时就保留为字符串,不要顺手转成整数,公开数据集的 ID 为了匿名化往往有前缀或长度变化,转数值容易把不同实体错并到一起。容器层是中间层,字段包含容器 ID、所在机器 ID、申请的 CPU 和内存大小。任务层在最上层,字段包含任务 ID、关联容器 ID、入队时间、开始时间、结束时间、优先级和应用类型。
有个容易混淆的点:容器和任务不是严格一对一。一个容器可能在一段时间里被同一个任务反复使用,一个任务也可能被拆分成多个实例,分布到多个容器里。元数据里如果同时给出容器 ID 和任务 ID,不要直接拿其中一个当唯一主键,先做一次唯一性检查。
我一般按下面这张表快速建立字段印象:
| 文件层 | 实体 | 常见字段 | 主要用途 |
|---|---|---|---|
| 元数据 | 机器 | machine_id、cpu_total、mem_total、machine_type | 资源总量与异构程度 |
| 元数据 | 容器 | container_id、machine_id、cpu_request、mem_request | 超卖比例与资源申请 |
| 元数据 | 任务 | task_id、container_id、create_time、end_time、priority | 调度请求与生命周期 |
字段命名在不同发布版本里可能有差异,含义基本都落在上表范围内。拿到新版本先不写业务逻辑,用 head 看前五行,把实际字段名列出来再映射。
2.2 usage 文件的时间窗口:先做差分,再决定怎么对齐
usage 文件保存动态统计数据。常见实现是周期性采集,间隔可能是几秒也可能是几分钟,落到文件里就成了带时间戳的采样点。拿到 usage 文件后,第一步不是算均值,而是检查时间窗口长度,把相邻时间戳做差分,看最小值和中位数。如果两者相等,说明是一条等宽时间线;如果不相等,说明存在漏采或提前停止的机器。这个判断直接决定后续聚合用固定窗口重采样,还是按实际时间区间去 join。
时间戳的绝对语义也值得确认。公开 trace 普遍会对时间做偏移,也就是把原始时间统一减去一个基准值,这样你看到的时间戳往往是相对值而不是真实 Unix 时间。做研究时这完全够用,只要统一用“相对时间差”来对齐,不要去换算真实日期;如果同时用了两份不同版本的 trace,更不能拿绝对时间戳直接 join,必须按相对偏移重新同步。
usage 文件里还有一类常见字段是负载均值,类似 loadavg。它和 cpu 使用率是两套口径:cpu 使用率描述的是“这核有多少时间在跑指令”,负载均值则和内核运行队列长度强相关。做集群管理分析时,尽量以标注为 cpu_rate 或 cpu_usage 的字段做主口径,loadavg 只作为排队压力的旁证。
2.3 先分清三套资源数字:申请量、使用量、容量
这节必须单独说,因为后续所有实验设计都取决于口径。资源容量是机器标注的 cpu_total 与 mem_total;申请量是容器元数据里的 cpu_request 与 mem_request;使用量是 usage 文件里采到的实际负载。生产集群经常超卖,即申请量之和大于容量,目的是提高资源利用率。
做调度模拟的准入控制时用申请量;做资源利用率画像时用使用量;做容量规划时两者都要。超卖比例的计算也简单,把同一时刻所有容器申请量求出来,和机器容量算比值。建议把所有统计统一成“按时间窗的每机汇总”之后再进行。
我写统计流程时,会先把每个表的唯一键和时间字段打上标签,用一个公共函数去初始化读取,避免后续每个实验单独解析时弄混口径。
3. 把clusterdata原始文件清洗成实验样本:解析、对齐、聚合三步
3.1 元数据解析:把类型、缺失值和重复键一次处理完
如果你习惯翻开 CSV 就直接 read_csv,拿到 clusterdata 这样的文本文件裸读会吃暗亏。文本文件里常见的缺失占位符是空字符串、\N、NULL,ID 列如果不声明类型会被当成整数,等到 join 的时候才发现两边精度不一致。
我一般这样处理:显式声明主键列为字符串,数字列转成 float,时间列先按字符串读进来,随后统一解析。
import pandas as pd task_meta = pd.read_csv( "task_meta.csv", sep=",", dtype={ "task_id": str, "container_id": str, "machine_id": str, "priority": "int8", }, na_values=["", "\\N", "NULL"], parse_dates=["create_time", "end_time"], ) print(task_meta.shape) print(task_meta.dtypes) print(task_meta.isna().sum()[task_meta.isna().sum() > 0]) print(task_meta.groupby("task_id").size().value_counts().head())逻辑说明:先看缺失值分布,再看 task_id 是否有重复。最后一行输出重复频次,如果大量 task_id 出现多次,说明 task_id 不是唯一的调度原语;数据里可能存在重试或实例展开,后面做负载重放时要按实例数展开,而不是按任务数直接模拟。
参数说明:na_values 里必须写上数据集中实际使用的缺失标记,不同版本可能用 \N,也可能用 NA,先用 head 看一眼文件头再填。dtype 里把 priority 压成 int8 是因为优先级通常只有几个档位。
3.2 usage 文件对齐到统一时间窗:按机器重采样,遇到空缺先查原因
usage 是时间序列,而且是大规模时间序列。直接把整张表 join 到 meta 上会占用大量内存,所以先把 usage 聚合到统一时间窗,再把窗口特征和 meta 合并。
usage["ts_window"] = usage["ts"].dt.floor("5min") usage_agg = ( usage.groupby(["machine_id", "ts_window"])["cpu_usage"] .mean() .reset_index() ) machine_ids = usage_agg["machine_id"].unique() all_windows = pd.date_range( usage_agg["ts_window"].min(), usage_agg["ts_window"].max(), freq="5min", ) full_index = pd.MultiIndex.from_product( [machine_ids, all_windows], names=["machine_id", "ts_window"], ) usage_aligned = ( usage_agg.set_index(["machine_id", "ts_window"]) .reindex(full_index) .reset_index() )逻辑说明:这一步把散落的采样点规整成“每台机器每个五分钟窗口一行”。先按机器和时间分组求均值,再把时间轴补全。补出来的空缺多数情况是机器在当天某段时间没上报,先保留 NaN,不要急着填零,因为“上报了但没负载”和“机器掉线”是两个完全不同的状态。
参数说明:freq="5min" 要提前通过时间戳差分判断窗口长度,如果实际粒度是 30 秒,就用 30s 或 1min;聚合窗口可以按实验需求放宽到 10min、30min。窗口越小,后面的仿真重放越细致,但数据量也会成倍上涨。
如果把 usage 窗口直接和 meta 合并,得到的就是一张既能看资源容量、又能看真实消耗的宽表,后面所有利用率计算都基于这张表。
3.3 生成基础特征:利用率、超卖率、碎片率的口径
拿到对齐宽表后,第一步是算三个基础指标。利用率是实际使用量与容量的比值,按机器算完再按集群聚合,避免大机器单台拉高整体。超卖率是申请量与容量之比,用来评估集群是否在“透支”资源。碎片率描述的是剩余资源里不可被大任务利用的部分,常见近似算法是把剩余 CPU 和剩余内存分别按比例折算,再算一个系数。三个指标要在同一个时间窗上计算,否则调度实验里看到的集群状态互相矛盾。
aligned["cpu_util"] = aligned["cpu_usage"] / aligned["cpu_total"] aligned["mem_util"] = aligned["mem_usage"] / aligned["mem_total"] cluster_usage = ( aligned.groupby("ts_window") .agg( cpu_util=("cpu_util", "mean"), cpu_p99=("cpu_util", lambda x: x.quantile(0.99)), ) .reset_index() )逻辑说明:按时间窗求集群平均利用率,同时保留 p99,用来观察是否有“局部热点”被平均掩盖。集群管理的结论不能只看均值,p99 往往才代表真实调度压力。
参数说明:p99 分位不建议直接写在聚合表达式里,而是先定义一个函数,方便把 p95、p90 一起算出来用于对比。
至此,数据准备阶段已经完成,接下来就是搭实验。
4. 用clusterdata做调度实验:负载重放、混合部署与指标闭环
4.1 把任务到达流映射成调度器的请求流
集群管理研究里最常规的实验是把 trace 变成模拟调度器的输入:任务是请求,机器是资源池,调度器决定请求放在哪台机器上。硬要模拟完整的容器启停、网络延迟,会引入大量噪声。我一般聚焦在资源请求层面做重放。
核心规则:一条任务记录在 create_time 进入系统,带有容器资源申请量,分配给一台机器,在 end_time 释放资源。为了把文本记录变成事件流,写一个小遍历即可。
events = [] for _, row in task_meta.iterrows(): if pd.isna(row["container_id"]): continue events.append({ "ts": row["create_time"], "type": "task_enter", "task_id": row["task_id"], "container_id": row["container_id"], "machine_id": row["machine_id"], "cpu_request": row["cpu_request"], "mem_request": row["mem_request"], }) events.sort(key=lambda x: x["ts"])逻辑说明:把任务开始时间当作请求到达时间,把数据里真实所在 machine_id 作为理想放置参考,而不是让模拟器自己重新调度一遍。这样重放出来的负载形态最接近真实集群。
参数说明:如果数据里 create_time 对每个实例重复,任务被拆成多个实例,则需要按实例数把请求复制 N 份;否则模拟器的压力会小于真实情况。
4.2 混合负载处理:优先级和应用类型决定实验分组
阿里生产集群长年跑混合负载,在线服务要求低延迟,离线任务重视吞吐,二者靠不同的优先级和资源配额区分。clusterdata 的元数据里通常会带优先级和应用类型字段,这两个字段决定了调度实验的分组方式,而不是把所有权当一类流量。
我常用的分组方法:把高优先级任务作为在线服务队列,把低优先级任务作为离线队列,两个队列共享剩余资源,但调度器保证先给在线服务预留额度。重新调度时,离线任务可以随时被抢占,在线服务则不做抢占。
online = task_meta[task_meta["priority"] >= PRIORITY_ONLINE_THRESHOLD] offline = task_meta[task_meta["priority"] < PRIORITY_ONLINE_THRESHOLD] print(online["cpu_request"].describe()) print(offline["cpu_request"].describe())逻辑说明:在搭实验前先对两个队列分别做统计描述,确认它们的资源请求分布确实不同。如果两批任务几乎一样,分组就没有意义。阈值怎么定:根据优先级字段的取值分布来定。优先级离散取值通常只有几个档位,看直方图从中位点切分即可,不用强行套算法。
4.3 实验指标闭环:接受率、等待时间与资源碎片
实验跑完后,不能只看单独的平均利用率,那样调度问题都被平均掉。我通常设三个核心指标。
第一个是请求接受率,从调度器角度看系统是否扛得住:被调度成功分配的任务数占总任务数的比例。第二个是等待时间,从任务角度看公平性:从 create_time 到被调度的时间差,取 p50 与 p99。第三个是资源碎片,从集群角度看连续性:模拟结束后统计剩余 CPU 与剩余内存的分布形状,判断资源是否被切得太碎。
sim_result["wait_ms"] = (sim_result["scheduled_time"] - sim_result["create_time"]).dt.total_seconds() / 1000 p50_wait = sim_result["wait_ms"].quantile(0.5) p99_wait = sim_result["wait_ms"].quantile(0.99) acceptance_rate = sim_result["scheduled"].sum() / len(sim_result)逻辑说明:这三个指标分别代表集群管理里互斥的三个目标:容量充足性、任务公平性、资源连续性。任何调度策略做对比,最后都要回到这三个数上。到了这一步,前面字段口径和窗口对齐的基本功有没有做扎实,会直接反映在这些数字的合理性上。
5. clusterdata避坑实录:五个高频问题的现象、原因与解法
5.1 任务数量总比容器多,统计出来对不上
现象:按 task_meta 统计任务数,按 container_meta 统计容器数,两者差距很大,负载重放时模拟器里的请求量远超真实集群。
原因:任务不等于容器。一个任务的多次实例或重试可能对应多个容器,而部分容器又可能被多个离线任务复用。
解决:先对 task_id 和 container_id 做唯一性检查。如果 task_id 大量重复,就用容器层作为资源请求的统计对象,任务层只保留调度语义。
5.2 机器 usage 时间窗大量空缺,补零后实验失真
现象:usage 对齐后出现整段 NaN,直接 fillna(0) 后机器看起来永远空闲,实验结论偏低。
原因:空缺可能来自机器维护、掉线或文件切分边界。补零把“没有数据”和“确实空闲”混为同一状态。
解决:先统计每台机器缺失比例,把缺失超过 30% 的机器标记为维护机,不纳入样本;未超过的窗口用前向填充,只将首尾缺失窗口剔除。
5.3 cpu_rate 和负载值相差很大,负载曲线对不上
现象:usage 文件里的 cpu 使用率和另一个类似 loadavg 的字段画出来,一个平缓一个尖刺,误以为数据异常。
原因:两者统计口径不同。cpu 使用率是 cpu 时间占比,负载值与排队任务数相关,不能相互验证。
解决:统一只用 cpu_rate 字段做资源利用率,loadavg 字段仅用于排队分析。如果做利用率对比,在实验文档里明确字段口径。
5.4 模拟器怎么调都跑不满,集群利用率虚低
现象:任务按记录被重放,但模拟器的资源利用率始终只有真实数据的一半。
原因:把“任务”当成“一个请求”来重放,忽略了任务内部的并行实例数。真实集群会把一个大任务拆成几十个实例,按容器维度排队运行。
解决:先计算 task_meta 中每个任务平均拥有的 container 数,在重放时按实例数展开请求,再基于请求类别进行调度。
5.5 跨版本换数据后脚本全乱,字段名对不上
现象:同一套脚本换一个版本后大量 KeyError,运行直接失败。
原因:发布版本调整了字段命名、表格切分方式或时间姿态,schema 变化没有提前做适配。
解决:在代码里加一层字段映射,把“常用名”统一映射到各版本实际字段,并在解析脚本里加断言,读取后检测必需字段是否存在。
6. 把clusterdata持续用起来:版本对比与个人复盘习惯
如果你打算把 clusterdata 当长期实验基座,我建议维护一个统一的 loader 接口,把不同版本的字段差异隔离在同一个读写层里。这样每次新数据到手,只需要改映射表,不用改实验逻辑。我会把版本号写进每次实验的配置,和结果一起保存,回头做对比时能准确说出“这个调度结论是在哪一版数据上成立的”。
第二个习惯是做端到端复盘。实验跑完后,把重放出来的请求总量、平均运行时长、机器平均利用率与原始数据里的统计值做一次对照,偏差超过 10% 就要回头查是窗口对齐问题还是实例展开问题。很多隐性 bug 都是在复盘时被发现的。
第三个值得坚持的验证方法,是用同一条 trace 跑两个完全不同的调度策略(比如先来先服务和保守的 DRF),对比它们的结果差异是否大于两台随机机器选择之间的方差。如果策略差异还不如随机噪声大,多半是数据清洗阶段就已经把特征抹平了。
这些都是我踩过几次坑后长期保留的习惯。数据研究里最怕的不是模型不收敛,而是实验输出了,但没有人能说清输出对应的是集群的哪一面。把字段、窗口、版本和指标口径钉死,clusterdata 才算真正被你用起来了。希望帮到你。
本文还有配套的精品资源,点击获取