启用 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 模块内部的判断路径。我用它给团队做培训,大家一下就明白了:

Call Caching 决策流程 Task 到达调度器 call-caching.enabled ? 直接执行(无缓存) 计算 task hash 数据库/缓存中匹配 hash ? 命中 → 复用缓存结果 未命中 → 执行并写入缓存 注意:即使命中,如果缓存结果曾被标记为“bad”,会回退执行。 invalidate-bad-cache-results=true 时,Cromwell 会自动处理这种情况。

避坑指南

  • 小心 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 没多复杂——无非是理解那三四个关键开关。但我在两次生产事故中栽了跟头之后,才真正重视这些细节。你照着上面的配置写,再结合流程图捋一遍逻辑,基本不会出大问题。