适用场景
这类方案适合手上有一批要跑几十分钟到几小时的长任务的人:批量文件处理、数据清洗、批量模型推理、报表生成等,步骤之间有先后依赖,中途断一次就得从头再来。团队还需要“同样的输入跑出同样的结果”,出了问题能定位到具体是哪一步。
Waymark 这类“编译式”工作流引擎的思路是:先把作业定义编译成一份带哈希的执行计划,再按计划执行;每一步的产物与状态都保存到磁盘,重跑时自动跳过已完成且输入未变的步骤。它要解决的核心问题就是长任务的断点续跑、失败重试和结果可复现。
环境与前置条件
- 操作系统:Linux 或 macOS;Windows 建议在 WSL2 里操作。
- 运行时:示例脚本使用 Python 3,请先确认
python3 --version可正常执行;Waymark 本体按官方文档给出的方式安装,具体版本要求以官方文档当前版本为准。 - 硬件:没有硬性门槛。CPU 4 核以上、内存 8 GB 起比较从容;如果流水线里含模型推理,按模型自身要求准备显存,并把推理单独拆成一个作业,方便单独限制并发。
- 磁盘:状态库、缓存、中间产物都会占空间,经验值是原始数据大小的 2~3 倍,长任务要额外预留。
- 其他:能稳定访问依赖的数据源;一个独立的项目目录;作业定义建议放进 Git 仓库管理。
分步骤部署
第 1 步:安装并确认 CLI 可用
按官方文档的方式安装完成后,先确认命令行工具本身可用:
```bash
waymark --version
waymark --help
```
成功标志:--version 打印出版本信息,--help 列出可用子命令。不同版本的子命令名可能略有差异,下文用到的 compile、run、status、logs 请先和 --help 对一遍,以自己的版本为准。
第 2 步:初始化项目
```bash
mkdir -p ~/work/long-pipeline && cd ~/work/long-pipeline
waymark init --name long-pipeline
```
这一步会生成工作区骨架:作业定义目录(示例里叫 jobs/)、全局配置文件(示例里叫 waymark.yaml)、隐藏的状态目录(.waymark/,存放编译出的计划、运行状态、日志和缓存)。
成功标志:ls -a 能看到这三样;waymark status 不报错,只提示还没有运行记录。
把 jobs/ 纳入版本控制,.waymark/ 下的运行产物不要提交;但计划文件建议单独复制一份归档,方便日后复盘某次结果是怎么算出来的。
第 3 步:定义第一个作业(抽取)
新建 jobs/01-extract.job.yaml:
```yaml
name: extract
description: 把原始文件汇总成清单
inputs:
- name: source_dir
type: path
default: ./data/raw
outputs:
- name: manifest
path: ./data/staging/manifest.json
command: |
python3 scripts/extract.py \
--src "${source_dir}" \
--out ./data/staging/manifest.json
timeout: 2h
retry:
max_attempts: 3
backoff: exponential
initial_delay: 30s
resources:
cpu: 1
```
几个要点:
inputs参与步骤键的计算,输入变了这一步就必须重算;outputs声明产物位置,引擎据此判断产物是否齐全。command要尽量幂等:同样的输入跑两次,产出同样的结果。做不到幂等就别开自动重试,否则会写出重复数据。timeout给长任务保留一个上限,避免它一直挂到第二天早上。
第 4 步:定义依赖作业与批处理
新建 jobs/02-infer.job.yaml,用 needs 声明依赖:
```yaml
name: infer
needs: [extract]
inputs:
- name: manifest
from: extract.manifest
- name: batch_size
type: int
default: 200
outputs:
- name: results
path: ./data/out/results/
command: |
python3 scripts/infer.py \
--manifest "${manifest}" \
--out ./data/out/results/ \
--batch-size "${batch_size}"
timeout: 6h
retry:
max_attempts: 2
backoff: exponential
initial_delay: 60s
```
from: extract.manifest 表示这个输入直接引用上游作业的产物,引擎会在执行前校验它是否存在。批处理脚本里建议按批次写文件,例如 results/batch-0001.jsonl,这样中途失败重跑时只需补算缺的批次,而不是整个作业从头来一遍。
再建一个 03-report.job.yaml,写 needs: [infer],把结果汇总成一份报告。三个作业连起来就是一张有向无环图。
第 5 步:编译成执行计划
这一步是“编译式”和边跑边解析的引擎之间的关键差别:
```bash
waymark compile --job report
```
编译阶段会解析全部作业定义,检查依赖是否齐全、有没有环、上下游的输入输出能不能对上,然后把整条链路固化成一份带内容哈希的计划文件,落在 .waymark/plans/ 下。
成功标志:输出类似 plan written to .waymark/plans/<hash>.plan.json,退出码为 0。如果依赖写错、出现循环依赖,错误在这一步就会暴露,不会等到任务跑到一半才出问题。
建议打开严格检查开关(具体参数名以 --help 为准),把警告当错误处理,并把计划文件交给同事 review。执行前就能看懂整条链路,是编译式流程相对解释式流程的主要收益。
第 6 步:干跑校验
```bash
waymark run --job report --dry-run
```
干跑只输出将要执行的步骤顺序、每步是命中缓存还是实际执行,不会真的运行命令。确认顺序是 extract -> infer -> report 再往下走。
第 7 步:正式执行
```bash
waymark run --job report --concurrency 2
```
--concurrency控制并行度。批量推理这类吃显存的步骤,压到 1 或 2;纯 IO 的步骤可以放开一些。- 终端会实时打印步骤状态。中途 Ctrl+C 或断网都没关系,状态库里保留的是“哪几步已经成功”。
成功标志:最后一行显示整条流水线成功,waymark status --job report 里所有步骤都是成功状态。
第 8 步:验证可重跑性
不要跳过这一步。立刻原样再跑一次:
```bash
waymark run --job report
```
预期:所有步骤都命中缓存或被跳过,几秒内结束,不会重新执行推理脚本。如果它又把推理跑了一遍,说明步骤键里混进了不确定的东西,常见的是命令里带了当前时间戳、随机数、临时目录路径,或者上游产物写入时文件修改时间参与了哈希。把这些不确定性去掉,可重跑才真正成立。
第 9 步:制造一次失败,验证重试与续跑
不必等真的网络挂掉。把 infer 的 max_attempts 临时改成 1,在 scripts/infer.py 开头加一行 raise SystemExit("simulated failure"),然后:
```bash
waymark run --job report --retry-failed
```
预期:extract 命中缓存直接跳过,infer 失败并记录日志,report 因上游失败被标记为跳过,而不是误报失败。把脚本改回去,再执行一次同样的命令:
```bash
waymark run --job report --retry-failed
```
预期:只有 infer 和 report 实际执行,extract 依旧跳过。这就是断点续跑的实际效果,长任务里代价很高的一种情况——从第一步重来——被避开了。
查看失败现场:
```bash
waymark logs --job report --step infer --tail 200
```
验证部署是否成功
按下面四条逐条确认,全过就算部署完成:
1. 编译能过:waymark compile --job report 退出码为 0,.waymark/plans/ 下出现新的计划文件。
2. 干跑顺序正确:waymark run --job report --dry-run 打印的步骤顺序与依赖一致,没有意外多出的步骤。
3. 重跑命中缓存:完整跑一次后再原样跑一次,第二次在很短时间内结束,状态显示全部跳过。
4. 失败能续跑:人为让中间步骤失败,修好后用 --retry-failed 只补跑失败步骤及其下游,上游不重算。
第 3、4 条是这套方案的核心价值。只过第 1、2 条说明流水线能跑起来,但还不能“可靠地重跑”,先别急着接真实生产数据。
常见报错与解决
1. Error: compiled plan is stale (source changed after compile)
→ 原因:改了作业定义或脚本,但没有重新编译,引擎拒绝用旧计划执行。
→ 解决:
```bash
waymark compile --job report
waymark run --job report
```
建议把“编译”固定进流程,比如在提交前检查里跑一次 compile,避免带着过期计划上生产。
2. Error: dependency cycle detected: extract -> infer -> extract
→ 原因:needs 写成了环,或者两个作业互相引用对方的产物。
→ 解决:把公共部分抽成一个没有上游的独立作业,两边都去 needs 它,然后重新编译验证:
```bash
waymark compile --job report
```
编译通过就说明环解开了。
3. step "infer" failed: exit status 1,日志里是连接超时或 Connection reset
→ 原因:外部数据源或服务抖动,属于典型的可重试错误。
→ 解决:确认脚本幂等后加重试策略,再手动补跑。
```yaml
retry:
max_attempts: 3
backoff: exponential
initial_delay: 60s
```
```bash
waymark run --job report --retry-failed
```
注意:如果失败发生在“文件写了一半”的时刻,先清理该批次的不完整产物再重跑,否则重试可能读到脏数据。
4. Error: state directory is locked by another run
→ 原因:同一个作业同时有两个进程在跑,比如上次的进程没退干净,终端关了但后台还在。
→ 解决:先确认没有真实在跑的任务,再处理锁。
```bash
ps aux | grep waymark
waymark run --job report --run-id manual-$(date +%s)
```
日常建议给定时任务和手动执行用不同的 --run-id,别让两条并发写同一份状态。
5. No space left on device 或产物写入失败
→ 原因:中间产物和缓存把磁盘占满,长任务尤其容易遇到。
→ 解决:先看占用分布,再用引擎自带的清理子命令清掉旧运行记录(具体子命令以 --help 为准),并把 outputs 指到容量更大的挂载点。
```bash
df -h .
du -sh .waymark/* | sort -h
```
后续维护
- 备份:至少要备份两样东西——作业定义(放 Git 仓库即可)和状态库(
.waymark/目录或外置数据库)。状态库丢了不影响计算结果正确性,但会让所有缓存失效,长任务等于重跑一遍,这个代价要提前算清楚。按天做快照比较稳妥。 - 升级:升级 CLI 前先用当前版本跑一次
compile并留存计划文件,升级后重新compile对比差异,确认没有语义变化再跑真实作业。跨大版本升级先在测试目录里跑通再动生产;版本兼容策略以官方文档当前版本为准。 - 日志:步骤日志按运行 ID 存放,定期归档或清理,并给日志目录设容量上限,避免长任务把盘写满。关键步骤建议在脚本里自己再打一行结构化日志(批次号、耗时、条数),排查时比翻引擎日志更快。
- 监控:盯三个指标——单步骤耗时(相比上次是否明显变慢)、失败率与重试次数、缓存命中率。缓存命中率突然掉到接近零,通常是输入里混进了时间戳、随机数之类的非确定性内容,发现得越早越省事。
- 并发与配额:吃资源的步骤单独限制并发,不要整条流水线共用一个并发数;给长步骤设合理
timeout,即便超时也至少能留下可诊断的日志。 - 文档:在仓库 README 里写清两条规矩——“改完定义先
compile再run”“重跑用--retry-failed”,新人上手会少踩很多坑。
