4. 缓存命中与未命中的判断逻辑
聊完缓存指纹,我们终于可以聊聊最核心的问题了:Cromwell 到底怎么判断一次任务是不是能命中缓存?
我以前刚接触这个时,觉得这应该是个黑盒,要么中要么不中。后来跟它斗智斗勇久了,才发现判断逻辑其实非常清晰。说白了就是 —— 拿着你新任务的指纹,去数据库里找一模一样的记录。
找到了,就是命中。找不到,就是不中。很直接,对吧?
但这里面有几个关键的“小机关”,不注意就容易翻车。我一个个拆给你看。
4.1 缓存命中的硬性条件
要命中缓存,必须同时满足下面三个条件。一个都不能少:
| 条件 | 说明 | 我的经验 |
|---|---|---|
| 指纹完全匹配 | 新任务的指纹,和在数据库里查到的指纹一模一样 | 字节差一位都不行,我见过因为换行符不同导致的未命中 |
| 任务元数据一致 | runtime参数、Docker镜像、命令模板等基本信息必须一致 | 换一个镜像tag,哪怕内容一样,也会判为未命中 |
| 输出文件存在且完整 | 缓存记录的输出文件,还在原来的路径下,而且没被删 | 我就干过手动清理临时目录导致缓存无效的蠢事 |
嗯,这里要特别提一下第三个条件。很多人以为数据库里有记录就行,其实不是。Cromwell 在返回缓存结果前,会确认输出文件是否还在。如果文件被清除了,它会老老实实重新跑任务,而不是给你一个空链接。
4.2 判断逻辑的两种路径
Cromwell 的判断逻辑,可以分成两条路:Cache Hit(命中)和Cache Miss(未命中)。
我们先看命中这条路:
- 第一步: 任务启动时,Cromwell 计算当前任务的指纹
- 第二步: 去 Cromwell 的元数据库(MySQL/PostgreSQL)里查找这个指纹
- 第三步: 找到匹配记录后,检查对应的输出文件是否存在且完整
- 第四步: 通过了,直接复用输出文件,不调度计算资源
再看未命中的路:
- 第一步: 计算指纹,查找数据库
- 第二步(分叉1): 数据库里根本没这个指纹 → 未命中
- 第二步(分叉2): 指纹找到了,但输出文件缺失/损坏 → 未命中
- 第三步: 正常提交任务,让Worker执行
你想想看,这两个分叉在实践中哪个更容易出现?我个人经验,分叉2(文件问题)比分叉1(新任务)要隐蔽得多。至少有三四次,我在排查“为什么缓存没生效”时,最后发现都是文件被清理了,而不是指纹本身有问题。
指纹匹配 + 文件存在 = 命中
指纹不匹配或文件不存在 = 未命中
4.3 判断优先级:哪个属性权重更高?
有一次在团队内部讨论时,有人问我:“如果指纹和元数据冲突了,Cromwell 听谁的?”
这个问题问得很好。但实际情况是,Cromwell 的缓存机制里不存在属性冲突。指纹本身就是元数据的哈希结果,它们是绑定的。
不过,从排查顺序上来说,我建议你按这个优先级来排查:
- 先看命中率日志 —— Cromwell 会在日志中输出 cache hit/miss 统计
- 再查具体指纹 —— 找到未命中的任务指纹,和缓存记录对比
- 最后看文件 —— 去输出路径确认文件是否完好
这样一层层排除,效率最高。我曾经教过一个实习生按这个顺序排查,15分钟就定位到问题了。
4.4 实战中的“假未命中”
什么叫“假未命中”?就是你以为应该命中,但实际没命中。
最常见的场景,是 Workflow 版本更新。你更新了 WDL 文件,加了一行注释,或者改了一个参数名。这时任务的指纹就会变,缓存自然不命中。
我遇到过最夸张的一次,是因为在一个 WDL 里多加了一个空格。是的,一个空格导致了缓存未命中。因为 Cromwell 的指纹是按文件内容严格计算的。
--write-cache false。不过这个仅限测试环境使用,生产环境别这么干。
java -jar cromwell.jar run myworkflow.wdl --write-cache false
另一个常见的“假未命中”是不同的调用路径。同一个 WDL,如果是从不同的子 Workflow 里调用的,虽然任务本身一模一样,Cromwell 也会判为不同。
4.5 用日志快速判断命中状态
查缓存问题,最快的方法是看 Cromwell 的日志。我一般直接 grep 关键词:
grep -i "cache" cromwell.log | grep -i "hit\|miss"
输出会像这样:
[INFO] [2025-04-15 10:23:12] Cache hit for job my_task:1bb5d3c
[INFO] [2025-04-15 10:23:15] Cache miss for job my_job:4f2a8e1 (no matching record)
[INFO] [2025-04-15 10:23:18] Cache miss for job my_job:7c3b9d2 (output file missing)
看到了吧?未命中时,Cromwell 会告诉你为什么未命中。如果是“no matching record”,说明指纹不存在,是新任务。如果是“output file missing”,说明指纹有,但文件丢了。
这个区别非常关键。我在现场给客户排查缓存问题时,就是靠这个日志快速定位的。有一次,客户说“缓存一直不生效”,我一看日志,全是“output file missing”,再一问,原来是运维同事刚清理了临时目录。
4.6 一次完整的判断流程
我把一次完整判断的流程画在了下面,你可以对照着看:
4.7 现场排查小记
我记得有一次在生产环境排查缓存问题,用户反馈“改了输入文件后缓存不更新”。我当时第一反应是:换输入文件当然不会命中缓存了,因为指纹变了啊。
但用户坚持说“输入文件没变,只是改了输出路径”。我仔细一看,果然,用户输出路径改了。这导致文件完整性检查时,Cromwell 找不到旧路径的文件,以为缓存失效了。但用户的想法是“我要换一个地方存结果,但要用缓存的计算结果”。
这里有个原则需要搞清楚:缓存命中的前提是输出路径不变。如果你想要缓存的计算结果,但放到不同的输出路径,目前不支持。你需要先把结果从缓存目录复制过去。
Cache result for call,它会告诉你完整的判断过程和原因。
grep "Cache result for call" cromwell.log | tail -20
好了,缓存判断逻辑就这些。抓到关键点其实很简单:指纹 + 文件 = 命中。记住这个公式,排查起来就有方向了。