启用 Call Caching 的基础配置
Call Caching 说白了就是 Cromwell 的“记忆功能”。同一个 task,同样的输入和命令,第一次跑完结果就被缓存下来。下次再遇到完全一样的请求,它就直接返回历史产出,不再重新算一遍。我在一个生产环境里见过一套工作流重复跑了 7 次相同的步骤——浪费了 40 多个 CPU 小时。所以这个功能值得好好配置。
核心配置项
启用 Call Caching 其实不需要改代码,改 Cromwell 的配置文件就行。下面是几个最关键的参数,我习惯把它们放在 cromwell.conf 的 call-caching 段落里:
| 配置项 | 默认值 | 说明 |
|---|---|---|
enabled |
true | 总开关,false 会完全禁用缓存 |
invalidate-bad-cache-results |
true | 如果缓存结果执行失败,自动标记为无效并重跑 |
write-to-cache |
true | 新执行的结果是否写入缓存 |
read-from-cache |
true | 是否允许命中已有的缓存 |
max-failed-cache-reads |
10 | 某 task 连续读取缓存失败多少次后停止尝试 |
我的个人经验:
默认 invalidate-bad-cache-results = true 是明智的。我曾经在一个集群上发现缓存文件被意外损坏,结果所有后续任务都直接读取了坏缓存,跑出来的数据全是错的。这个参数会自动把坏缓存打上无效标记,下次遇到相同的 task 就会重新执行。
配置示例
完整的 call-caching 配置块长这样。我个人习惯写在 cromwell.conf 的 call-caching 顶层键下:
call-caching {
enabled = true
invalidate-bad-cache-results = true
write-to-cache = true
read-from-cache = true
max-failed-cache-reads = 10
# 可选:缓存策略(默认是“hash-based”)
strategy = "hash-based"
# 可选:指定哪些任务要跳过缓存检查
# blacklist-cache-reads = ["my_task_name"]
}
小技巧:
你可以在某些 task 名称特别长或者输入有随机数的场景下,临时把 read-from-cache 设为 false,避免 Cromwell 做无意义的哈希比对。等排查清楚再改回来。
WDL 层面的缓存控制
Cromwell 的缓存判定基于三个要素:命令、输入文件内容、运行时属性(docker image、CPU、内存等)。WDL 本身没有专门控制缓存的语法,但你可以通过 workflow options 来干预:
{
"read_from_cache": true,
"write_to_cache": true
}
这个 JSON 文件在提交工作流时通过 --options 参数传入。为什么需要这个?我记得有一次做批量分析,同一套 WDL 在开发阶段和正式生产阶段要使用不同的缓存策略:开发时每次都重新执行(关掉读缓存),生产时打开。所以我在 options 里控制,而不是改配置重启 Cromwell。
注意:
Workflow options 中的 read_from_cache 和 write_to_cache 会覆盖配置文件里对应的值。但如果 cromwell.conf 中 enabled = false,options 里的参数也不会生效。记住总开关优先级最高。
缓存命中决策流程
下面这张图展示了一个 task 被 Cromwell 调度时,Call Caching 模块内部的判断路径。我用它给团队做培训,大家一下就明白了:
避坑指南
- 小心 Docker 镜像差异: 缓存 hash 包含了 docker 镜像。如果你在 WDL 里写
docker: "ubuntu:latest",两次提交之间镜像更新了,hash 就不会匹配。我遇到过因为镜像 tag 没固定导致缓存永远打不中的悲剧。建议用精准的 tag 或 digest。 - 文件路径的影响: 同样内容的输入文件,如果放在不同路径下,Cromwell 会对路径做规范化处理。但某些后端(比如本地)可能会把绝对路径也纳入 hash 计算。我在迁移到 PBS 集群时,因为输出目录不同,相同任务居然没命中缓存。后来发现需要配置
call-caching.file-hash-strategy = "file-hash"才能只比对文件内容。 - 不要随意关闭读写缓存: 有些新手为了加速调试,把
write-to-cache设为 false。结果正式跑时没缓存可用,白白浪费大量计算资源。除非你明确知道自己在做什么,否则保持默认 true。
其实配置 Call Caching 没多复杂——无非是理解那三四个关键开关。但我在两次生产事故中栽了跟头之后,才真正重视这些细节。你照着上面的配置写,再结合流程图捋一遍逻辑,基本不会出大问题。