PySpark 的优势随规模扩大,但不是统一切换
从 100 万、1000 万到 1 亿行,PySpark 计算耗时更低的任务由 0 → 6 → 9 / 12 增加,端到端则为 0 → 2 → 12 / 12。到 1 亿行,完整处理的耗时差达到 3.27–5.77 倍。
选型启示:规模是第一层筛选,任务类型与计时范围决定最后的选择。这里的“胜出”按中位数计数,也包含接近持平的任务。
这组实验不只回答“谁更快”:优势出现在哪类任务、为什么计算与完整处理会给出相反的选择、哪些差距还不足以下结论。下面按总体趋势、任务差异、计时口径、扩展表现、工作量结构和证据强度逐层展开。
从 100 万、1000 万到 1 亿行,PySpark 计算耗时更低的任务由 0 → 6 → 9 / 12 增加,端到端则为 0 → 2 → 12 / 12。到 1 亿行,完整处理的耗时差达到 3.27–5.77 倍。
选型启示:规模是第一层筛选,任务类型与计时范围决定最后的选择。这里的“胜出”按中位数计数,也包含接近持平的任务。
1000 万行时,只有数值排序和字符串排序的端到端耗时是 PySpark 更低,分别只需 pandas 耗时的 80% / 82%。同规模去重虽在计算上快 2.19 倍,端到端却是 pandas 更快(5.54 vs 13.39 秒)。
选型启示:三档规模只能给出优势变化的区间,不能推导“超过某个固定行数就换 PySpark”。
1 亿行过滤,pandas 计算为 9.26 秒,低于 PySpark 的 14.57 秒;端到端却是 110.36 vs 24.78 秒,PySpark 快 4.45 倍。
选型启示:数据已驻留、连续做运算时参考计算模式;从启动到结果文件交付时参考端到端。该反转说明只优化算子不足以预测完整处理时间,具体原因需阶段证据;两种独立运行不能相减拆分开销。
从 100 万到 1 亿行,求和与流水线的 pandas 计算耗时分别增加 105.8 / 103.7 倍,PySpark 则为 3.52 / 3.64 倍。小规模的耗时排序因此不能直接外推到大规模。
选型启示:至少用接近业务规模的数据验证。固定成本摊薄与并行执行是可能解释,但这些曲线不能单独分离两者,也不能证明更大规模仍按同样比例增长。
1 亿行时,流水线只选 3 列、筛选约 1% 的输入再聚合,最终输出 100 行、2 列;过滤任务保留全列,输出约 5000 万行、10 列。PySpark 计算分别为 1.60 / 14.57 秒;流水线相对 pandas 的优势为 7.94 倍。
选型启示:输入行数之外,还要对齐筛选比例、列宽和输出规模。不能将这个“先筛选再聚合”的结果泛化为所有多步骤流水线;这里并非只改变一个因素的对照实验。
1 亿行的小表内连接与大表内连接,计算中位数仅相差约 3.2% / 2.9%(以 PySpark 耗时为分母)。大表内连接的两次值范围为 pandas 28.80–34.74 秒、PySpark 30.67–31.07 秒,两者重叠;小表连接也一样。
选型启示:将这两项计算表现视为接近,增加独立重复再判断。观测范围不是置信区间;端到端的明显差距应单独评估。
| 规模 | 任务 | 模式 | pandas | PySpark |
|---|---|---|---|---|
| 1000 万行 | 数值排序 | 计算 | 12.1898两次实测:12.5241 / 11.8555 | 4.0756两次实测:4.0480 / 4.1033 |
| 1000 万行 | 数值排序 | 端到端 | 24.7939两次实测:24.8109 / 24.7770 | 19.7502两次实测:19.5708 / 19.9297 |
| 1000 万行 | 字符串排序 | 计算 | 12.0973两次实测:12.1704 / 12.0241 | 4.4150两次实测:4.2916 / 4.5383 |
| 1000 万行 | 字符串排序 | 端到端 | 24.3391两次实测:24.2715 / 24.4066 | 20.0108两次实测:19.8607 / 20.1609 |
| 1000 万行 | 去重 | 计算 | 1.8722两次实测:1.8740 / 1.8705 | 0.8537两次实测:0.8785 / 0.8290 |
| 1000 万行 | 去重 | 端到端 | 5.5381两次实测:5.5173 / 5.5590 | 13.3922两次实测:13.4520 / 13.3325 |
| 10000 万行 | 过滤 | 计算 | 9.2647两次实测:8.9730 / 9.5564 | 14.5749两次实测:14.4408 / 14.7091 |
| 10000 万行 | 过滤 | 端到端 | 110.3616两次实测:111.2124 / 109.5108 | 24.7810两次实测:24.9261 / 24.6359 |
| 100 万行 | 求和 | 计算 | 0.0295两次实测:0.0296 / 0.0294 | 0.2334两次实测:0.1951 / 0.2716 |
| 10000 万行 | 求和 | 计算 | 3.1204两次实测:2.7580 / 3.4829 | 0.8223两次实测:0.8403 / 0.8043 |
| 100 万行 | 组合任务 | 计算 | 0.1227两次实测:0.1224 / 0.1230 | 0.4397两次实测:0.4665 / 0.4129 |
| 10000 万行 | 组合任务 | 计算 | 12.7265两次实测:12.1639 / 13.2891 | 1.6025两次实测:1.7694 / 1.4356 |
| 10000 万行 | 小维表连接 | 计算 | 29.3598两次实测:27.1169 / 31.6027 | 30.3452两次实测:29.7313 / 30.9591 |
| 10000 万行 | 内连接 | 计算 | 31.7701两次实测:28.7994 / 34.7407 | 30.8703两次实测:31.0745 / 30.6661 |
结论基于本次单机 24 核、12 个 workload 的正式耗时中位数;每配置两次重复。下方可切换数据规模及单核对照,较小差异不代表统计显著。
数据已加载并完成预热,从开始执行任务计到完整结果物化。不包括容器启动、加载、预热和结果写盘。
从启动容器开始,覆盖数据加载、计算、Parquet 结果输出以及 fsync(确保写入完成),用于比较一次完整任务的等待时间。
例:读取一份订单文件,把金额列求和,再把总金额保存为结果文件。同样的任务,两种模式的“秒表”覆盖不同步骤:
计算模式:先启动环境、读入订单并完成预热,再开始计时 → 对金额列求和,直到总金额真正算出 → 停止计时。之后保存结果的时间不计入。它回答的是:数据已经准备好,执行这次运算有多快?
端到端模式:开始计时 → 启动容器和引擎 → 读取订单 → 求和 → 将结果写为 Parquet 文件并完成 fsync → 停止计时。它回答的是:从启动这次任务,到拿到落盘的结果,一共要等多久?
两种模式来自独立运行,并非同一次运行的嵌套计时;不要直接相减来估计启动或 I/O 耗时。下图条形及数值表示两次测量的中位数,实心点 ● / 空心点 ○ 分别表示第 1 / 2 次真实测量。
任务按相同顺序纵向排列,每个任务用两根横向条形比较引擎。同图所有任务共用从 0 开始的线性时间轴;端到端与计算两张图的横轴范围分别设置,请按秒数比较。点击各图图例可筛选引擎,两次原始耗时见完整数据表。每配置只有两次重复,小差异不代表统计显著。
每次运行各自从计时开始对齐到 0 秒:计算模式从加载、预热后开始,端到端模式从容器启动开始。曲线不拉伸、不平均,也不延长到其他运行结束。
| 行数 | 任务 | 模式 | 核数 | 来源 | pandas ① | pandas ② | 中位数 | PySpark ① | PySpark ② | 中位数 |
|---|---|---|---|---|---|---|---|---|---|---|
| 1000000 | 求和 | 计算 | 24 | 原正式 | 0.0296 | 0.0294 | 0.0295 | 0.1951 | 0.2716 | 0.2334 |
| 1000000 | 求和 | 端到端 | 24 | 原正式 | 1.5818 | 1.5475 | 1.5646 | 11.4703 | 11.1433 | 11.3068 |
| 1000000 | 过滤 | 计算 | 24 | 原正式 | 0.0709 | 0.0703 | 0.0706 | 0.6576 | 0.5533 | 0.6054 |
| 1000000 | 过滤 | 端到端 | 24 | 原正式 | 1.9946 | 1.9791 | 1.9868 | 11.4175 | 11.5904 | 11.5039 |
| 1000000 | 去重 | 计算 | 24 | 原正式 | 0.1814 | 0.1806 | 0.1810 | 0.4777 | 0.5843 | 0.5310 |
| 1000000 | 去重 | 端到端 | 24 | 原正式 | 1.7003 | 1.6908 | 1.6956 | 12.8537 | 12.9091 | 12.8814 |
| 1000000 | 数值排序 | 计算 | 24 | 原正式 | 0.6000 | 0.6014 | 0.6007 | 1.2445 | 1.2465 | 1.2455 |
| 1000000 | 数值排序 | 端到端 | 24 | 原正式 | 2.9218 | 2.9237 | 2.9228 | 15.6545 | 15.2852 | 15.4699 |
| 1000000 | 字符串排序 | 计算 | 24 | 原正式 | 0.5025 | 0.4828 | 0.4926 | 1.2315 | 1.2503 | 1.2409 |
| 1000000 | 字符串排序 | 端到端 | 24 | 原正式 | 2.7875 | 2.7939 | 2.7907 | 15.1832 | 14.8091 | 14.9962 |
| 1000000 | 低基数分组 | 计算 | 24 | 原正式 | 0.0729 | 0.0749 | 0.0739 | 0.4100 | 0.4951 | 0.4526 |
| 1000000 | 低基数分组 | 端到端 | 24 | 原正式 | 1.5872 | 1.5862 | 1.5867 | 12.5220 | 12.4093 | 12.4657 |
| 1000000 | 多列分组 | 计算 | 24 | 原正式 | 0.2230 | 0.2212 | 0.2221 | 0.5732 | 0.5828 | 0.5780 |
| 1000000 | 多列分组 | 端到端 | 24 | 原正式 | 1.7483 | 1.7414 | 1.7449 | 13.5496 | 13.4481 | 13.4988 |
| 1000000 | 高基数分组 | 计算 | 24 | 原正式 | 0.0495 | 0.0488 | 0.0492 | 0.4458 | 0.5170 | 0.4814 |
| 1000000 | 高基数分组 | 端到端 | 24 | 原正式 | 1.5535 | 1.5663 | 1.5599 | 13.0109 | 12.9630 | 12.9869 |
| 1000000 | 小维表连接 | 计算 | 24 | 原正式 | 0.2374 | 0.2340 | 0.2357 | 1.1329 | 1.2041 | 1.1685 |
| 1000000 | 小维表连接 | 端到端 | 24 | 原正式 | 2.6301 | 2.6135 | 2.6218 | 12.5361 | 12.4725 | 12.5043 |
| 1000000 | 内连接 | 计算 | 24 | 原正式 | 0.2248 | 0.2197 | 0.2222 | 0.9857 | 1.0149 | 1.0003 |
| 1000000 | 内连接 | 端到端 | 24 | 原正式 | 2.5374 | 2.5522 | 2.5448 | 13.2649 | 13.3035 | 13.2842 |
| 1000000 | 左连接 | 计算 | 24 | 原正式 | 0.1405 | 0.1427 | 0.1416 | 1.0844 | 1.0108 | 1.0476 |
| 1000000 | 左连接 | 端到端 | 24 | 原正式 | 2.5122 | 2.4825 | 2.4974 | 13.0431 | 13.2150 | 13.1290 |
| 1000000 | 组合任务 | 计算 | 24 | 原正式 | 0.1224 | 0.1230 | 0.1227 | 0.4665 | 0.4129 | 0.4397 |
| 1000000 | 组合任务 | 端到端 | 24 | 原正式 | 1.6217 | 1.6767 | 1.6492 | 12.9848 | 12.9036 | 12.9442 |
| 10000000 | 求和 | 计算 | 24 | 原正式 | 0.2941 | 0.2965 | 0.2953 | 0.4133 | 0.4174 | 0.4154 |
| 10000000 | 求和 | 端到端 | 24 | 原正式 | 3.9540 | 3.9363 | 3.9451 | 12.1744 | 12.0068 | 12.0906 |
| 10000000 | 过滤 | 计算 | 24 | 原正式 | 0.7481 | 0.7505 | 0.7493 | 1.3563 | 1.2078 | 1.2820 |
| 10000000 | 过滤 | 端到端 | 24 | 原正式 | 8.8920 | 8.8928 | 8.8924 | 14.2287 | 14.2518 | 14.2402 |
| 10000000 | 去重 | 计算 | 24 | 原正式 | 1.8740 | 1.8705 | 1.8722 | 0.8785 | 0.8290 | 0.8537 |
| 10000000 | 去重 | 端到端 | 24 | 原正式 | 5.5173 | 5.5590 | 5.5381 | 13.4520 | 13.3325 | 13.3922 |
| 10000000 | 数值排序 | 计算 | 24 | 原正式 | 12.5241 | 11.8555 | 12.1898 | 4.0480 | 4.1033 | 4.0756 |
| 10000000 | 数值排序 | 端到端 | 24 | 原正式 | 24.8109 | 24.7770 | 24.7939 | 19.5708 | 19.9297 | 19.7502 |
| 10000000 | 字符串排序 | 计算 | 24 | 原正式 | 12.1704 | 12.0241 | 12.0973 | 4.2916 | 4.5383 | 4.4150 |
| 10000000 | 字符串排序 | 端到端 | 24 | 原正式 | 24.2715 | 24.4066 | 24.3391 | 19.8607 | 20.1609 | 20.0108 |
| 10000000 | 低基数分组 | 计算 | 24 | 原正式 | 0.7725 | 0.7732 | 0.7729 | 0.5767 | 0.6274 | 0.6020 |
| 10000000 | 低基数分组 | 端到端 | 24 | 原正式 | 4.5681 | 4.4164 | 4.4923 | 12.9398 | 12.9017 | 12.9208 |
| 10000000 | 多列分组 | 计算 | 24 | 原正式 | 2.0877 | 2.0355 | 2.0616 | 0.9948 | 0.8967 | 0.9458 |
| 10000000 | 多列分组 | 端到端 | 24 | 原正式 | 5.7397 | 5.8019 | 5.7708 | 13.9110 | 14.2095 | 14.0602 |
| 10000000 | 高基数分组 | 计算 | 24 | 原正式 | 0.7821 | 0.6680 | 0.7250 | 0.8111 | 0.9210 | 0.8660 |
| 10000000 | 高基数分组 | 端到端 | 24 | 原正式 | 4.4330 | 4.3821 | 4.4076 | 14.1490 | 14.2053 | 14.1771 |
| 10000000 | 小维表连接 | 计算 | 24 | 原正式 | 2.7097 | 2.6996 | 2.7047 | 2.5248 | 3.1877 | 2.8563 |
| 10000000 | 小维表连接 | 端到端 | 24 | 原正式 | 15.7413 | 15.6153 | 15.6783 | 16.3973 | 15.1861 | 15.7917 |
| 10000000 | 内连接 | 计算 | 24 | 原正式 | 2.6902 | 2.5359 | 2.6131 | 4.2196 | 4.1734 | 4.1965 |
| 10000000 | 内连接 | 端到端 | 24 | 原正式 | 14.7951 | 14.8178 | 14.8064 | 19.0175 | 18.6899 | 18.8537 |
| 10000000 | 左连接 | 计算 | 24 | 原正式 | 1.4634 | 1.4352 | 1.4493 | 4.2773 | 3.8993 | 4.0883 |
| 10000000 | 左连接 | 端到端 | 24 | 原正式 | 15.0555 | 14.5591 | 14.8073 | 20.2869 | 19.4103 | 19.8486 |
| 10000000 | 组合任务 | 计算 | 24 | 原正式 | 1.2293 | 1.2276 | 1.2284 | 0.5733 | 0.5937 | 0.5835 |
| 10000000 | 组合任务 | 端到端 | 24 | 原正式 | 4.8982 | 5.0520 | 4.9751 | 13.0312 | 13.1796 | 13.1054 |
| 100000000 | 求和 | 计算 | 24 | 原正式 | 2.7580 | 3.4829 | 3.1204 | 0.8403 | 0.8043 | 0.8223 |
| 100000000 | 求和 | 端到端 | 24 | 原正式 | 48.7097 | 48.2121 | 48.4609 | 12.6345 | 12.6805 | 12.6575 |
| 100000000 | 过滤 | 计算 | 24 | 原正式 | 8.9730 | 9.5564 | 9.2647 | 14.4408 | 14.7091 | 14.5749 |
| 100000000 | 过滤 | 端到端 | 24 | 原正式 | 111.2124 | 109.5108 | 110.3616 | 24.9261 | 24.6359 | 24.7810 |
| 100000000 | 去重 | 计算 | 24 | 原正式 | 18.3257 | 18.2875 | 18.3066 | 4.3532 | 4.2354 | 4.2943 |
| 100000000 | 去重 | 端到端 | 24 | 原正式 | 66.4186 | 65.8090 | 66.1138 | 15.7097 | 15.5907 | 15.6502 |
| 100000000 | 数值排序 | 计算 | 24 | 原正式 | 152.0517 | 147.4978 | 149.7747 | 36.2102 | 36.3332 | 36.2717 |
| 100000000 | 数值排序 | 端到端 | 24 | 原正式 | 307.4599 | 304.0218 | 305.7409 | 51.7259 | 54.2041 | 52.9650 |
| 100000000 | 字符串排序 | 计算 | 24 | 原正式 | 175.0588 | 173.4315 | 174.2452 | 45.4330 | 46.3240 | 45.8785 |
| 100000000 | 字符串排序 | 端到端 | 24 | 原正式 | 330.3462 | 321.2228 | 325.7845 | 64.8965 | 65.0349 | 64.9657 |
| 100000000 | 低基数分组 | 计算 | 24 | 原正式 | 7.7640 | 7.5424 | 7.6532 | 2.0631 | 1.7877 | 1.9254 |
| 100000000 | 低基数分组 | 端到端 | 24 | 原正式 | 54.7141 | 54.2245 | 54.4693 | 14.1400 | 13.9382 | 14.0391 |
| 100000000 | 多列分组 | 计算 | 24 | 原正式 | 21.1669 | 20.9623 | 21.0646 | 4.5466 | 4.4042 | 4.4754 |
| 100000000 | 多列分组 | 端到端 | 24 | 原正式 | 69.6693 | 69.0397 | 69.3545 | 17.3040 | 16.9073 | 17.1057 |
| 100000000 | 高基数分组 | 计算 | 24 | 原正式 | 12.3883 | 11.9181 | 12.1532 | 5.3952 | 5.3570 | 5.3761 |
| 100000000 | 高基数分组 | 端到端 | 24 | 原正式 | 69.8004 | 58.5817 | 64.1910 | 19.5429 | 19.7200 | 19.6315 |
| 100000000 | 小维表连接 | 计算 | 24 | 原正式 | 27.1169 | 31.6027 | 29.3598 | 29.7313 | 30.9591 | 30.3452 |
| 100000000 | 小维表连接 | 端到端 | 24 | 原正式 | 199.8728 | 191.0975 | 195.4851 | 39.0652 | 39.9504 | 39.5078 |
| 100000000 | 内连接 | 计算 | 24 | 原正式 | 28.7994 | 34.7407 | 31.7701 | 31.0745 | 30.6661 | 30.8703 |
| 100000000 | 内连接 | 端到端 | 24 | 原正式 | 180.0142 | 174.2984 | 177.1563 | 43.7971 | 46.5997 | 45.1984 |
| 100000000 | 左连接 | 计算 | 24 | 原正式 | 16.3811 | 17.4260 | 16.9035 | 34.5507 | 34.0939 | 34.3223 |
| 100000000 | 左连接 | 端到端 | 24 | 原正式 | 174.9924 | 180.1277 | 177.5601 | 49.4203 | 46.8140 | 48.1171 |
| 100000000 | 组合任务 | 计算 | 24 | 原正式 | 12.1639 | 13.2891 | 12.7265 | 1.7694 | 1.4356 | 1.6025 |
| 100000000 | 组合任务 | 端到端 | 24 | 原正式 | 68.6770 | 66.9722 | 67.8246 | 14.7331 | 14.5130 | 14.6231 |
| 10000000 | 求和 | 计算 | 1 | 原正式 | 0.2882 | 0.2952 | 0.2917 | 1.3231 | 1.5247 | 1.4239 |
| 10000000 | 数值排序 | 计算 | 1 | 原正式 | 10.9378 | 10.9720 | 10.9549 | 56.5561 | 54.8259 | 55.6910 |
| 10000000 | 高基数分组 | 计算 | 1 | 原正式 | 0.6527 | 0.6503 | 0.6515 | 2.9590 | 3.1651 | 3.0621 |
| 10000000 | 内连接 | 计算 | 1 | 原正式 | 2.5625 | 2.5462 | 2.5544 | 56.4050 | 52.6971 | 54.5510 |
| 10000000 | 过滤 | 计算 | 1 | 单核独立补测 | 0.7142 | 0.7122 | 0.7132 | 17.9332 | 17.6531 | 17.7931 |
| 10000000 | 去重 | 计算 | 1 | 单核独立补测 | 1.8440 | 1.8403 | 1.8422 | 5.1860 | 4.9858 | 5.0859 |
| 10000000 | 字符串排序 | 计算 | 1 | 单核独立补测 | 11.1610 | 11.2290 | 11.1950 | 65.8167 | 64.8177 | 65.3172 |
| 10000000 | 低基数分组 | 计算 | 1 | 单核独立补测 | 0.7580 | 0.7744 | 0.7662 | 2.5576 | 2.8244 | 2.6910 |
| 10000000 | 多列分组 | 计算 | 1 | 单核独立补测 | 2.0438 | 2.0360 | 2.0399 | 5.8237 | 5.8450 | 5.8344 |
| 10000000 | 小维表连接 | 计算 | 1 | 单核独立补测 | 2.6613 | 2.6494 | 2.6553 | 34.4216 | 35.5049 | 34.9633 |
| 10000000 | 左连接 | 计算 | 1 | 单核独立补测 | 1.3835 | 1.3790 | 1.3812 | 57.4647 | 58.9034 | 58.1841 |
| 10000000 | 组合任务 | 计算 | 1 | 单核独立补测 | 1.2142 | 1.2137 | 1.2139 | 2.4304 | 2.4663 | 2.4483 |
计算耗时排除启动、加载、预热和输出落盘,计时包含完整结果物化。端到端耗时包含容器启动、加载、计算、Parquet 输出及 fsync。两者来自独立运行。
CPU 为整机执行核数,不含 iowait / steal;内存为 MemTotal − MemAvailable;磁盘为 sda。CPU / I/O 的前后采样点都须落在计时窗口内。原约 1 秒采样超过 2.2 秒的缺口断开;高频采样的被拒绝区间及超过 20 ms 的间隔断开。无平滑、无插值,无样本留空。
正式矩阵为 3 规模 × 12 任务 × 2 引擎 × 2 模式 × 2 次 = 288 次,加单核 16 次。按原 seed108 随机队列每配置保留最先两次;第 1 / 2 次表示保留顺序,不等于原 repeat 标签。
304 次正确性通过。原始结果、采集归档、输入校验和与调度修订已验收。大输出保留于测试机,未包含在默认本地下载中。补充 profiling 不替代正式观测。
33 组计算配置,两引擎各两次,共 132 次;按原计算曲线任一资源不足 5 点选取。与原实验使用相同 worker、镜像、数据及正确性签名。补测只用于资源观测,正式耗时仍取原 304 次结果。计算曲线与端到端曲线按各自计时开始对齐为 0 秒。
每项至少 4 点,CPU / I/O 首尾有效点覆盖至少 45% 计算时长。Linux /proc CPU 计账粒度不会随采样频率提高;数毫秒尖峰不能解释为精确 CPU 峰值。零 I/O 可能是真实零值。
1000 万行、1 核、计算模式新增 8 个任务 × 2 引擎 × 2 次,共 32 次。与原单核 4 个任务合计 12 个;原正式 304 次不变。新补测的耗时与资源来自同一次运行,区别于旧 132 次仅用于资源的补测。每行来源及逐次证据可在完整数据表中查看。
worker 固定 CPU0,Spark local[1] / shuffle1,96g heap、128 GiB 内存。跨核数比较同时改变了并行度与 shuffle 分区,不能把差异全部归因于 CPU 数量。
采集器目标间隔 5 ms,位于 CPU47,与 worker CPU0 物理隔离。沿用原 24 核求和开销门槛,不声称为新的单核开销估计。32 次正确性签名均匹配原同任务参考;每项资源最少 141 点,最低覆盖率 98.28%。output / scratch 保留远端,未下载。
32 次本地核验 · 单核补测曲线清单 · 补测计划 · 证据包 SHA 与导出凭据 · 逐文件 SHA 清单
Campaign:20261025T130534Z-full
Worker source SHA-256:68ab7d2564bcb09b20271dfa49f88ce8023df6f097c74c06fba624cf5b2cf3b3
高频补测 Campaign:20261026-resource-highfreq-supplement;原始计划与采集入口随离线包附带,已完成实验不得重复启动。
新 campaign 复现:python3 .runtime/two-repeat-schedule.py --root . --campaign NEW_ID --reproduce