题目与背景
假设每个任务约执行 25 个 action,平均新增输出为 300 MB。重复构建经常共享输入,但错误命中可能让错误工具链或秘密参与编译的产物进入发布流程。缓存是优化项:未命中或缓存故障应回退到执行,错误命中则属于正确性事故。回答要把 action 结果查询与不可变产物存储分开,并说明远程执行如何改变信任和容量模型。
面试官考察什么
- 能否把影响确定性结果的输入完整纳入 action key。
- 能否分离可变的 action 元数据与不可变的内容寻址存储(CAS)。
- 能否做到原子写入、摘要校验,并阻止跨租户缓存投毒。
- 能否明确容量、垃圾回收、可观测性、故障降级和迁移。
- 能否区分构建缓存与通用键值缓存:正确性优先于接受旧读。
建议先问的澄清问题
先问构建是否 hermetic、支持哪些操作系统和架构,以及是否必须远程执行。确认保留时长、租户与仓库边界、最大产物大小、命中率目标、数据驻留要求,以及秘密或私有源码是否可能进入输出。确认结果能否跨分支、工具链版本共享,还是只能在 commit 与平台组合内共享;也要确认 CI 是否写入而开发机只读。
30 秒回答框架
我会把规范化后的 action 描述纳入哈希,包括命令、工具链和平台身份、声明输入的 Merkle 根、相关环境和构建配置。action cache 将 key 映射到输出摘要和结果元数据,CAS 按摘要保存不可变 blob。读取时校验元数据和每个 blob 后再落盘。成功的本地或远程执行先上传并校验 blob,最后发布 action 结果,避免部分产物变成命中。通过命名空间、认证写入、配额和沙箱阻止租户泄露。缓存失败返回未命中并受控执行,同时用指标和抽样干净重建发现错误命中。
分步深入
1. 定义 key 与存储边界
规范化 action 命令、编译器和链接器版本、平台、声明输入、相关 flag、白名单环境变量和外部依赖锁文件。用 Merkle 根表示输入树。排除秘密和不确定时间戳;非 hermetic action 应标记为不可缓存或限制在明确范围内。action 结果独立保存输出文件名、摘要、大小、退出码,以及可选的 stdout/stderr 摘要。CAS 对象不可变,只由摘要寻址。
2. 设计命中、未命中和发布路径
读取时按租户和 action key 命名空间路由到复制的元数据服务,取得结果后并行拉取缺失的 CAS blob。落盘前校验大小与摘要。未命中时在本地或沙箱 worker 执行。用幂等摘要写入上传已校验 blob,再以条件写入一次性提交 action 结果,确保引用的 blob 全部存在。并发写入可以上传同一个 blob,但只有完整结果可见。blob 或元数据不匹配时应当作为未命中并告警,不能返回成功命中。
3. 加入分布、隔离与安全
action 结果查询可用一致性哈希或元数据分片图,并在故障域之间复制。大体积 CAS 放入对象存储或分片 blob 层,热点元数据放入低延迟存储。每次请求认证并授权仓库和租户命名空间,开发机默认只读。传输和存储加密,执行远程 action 时使用沙箱和租户配额。除非策略明确允许,不跨租户去重;摘要不能绕过授权。
4. 规划容量和生命周期
题设负载是每天 20,000 个任务 × 25 个 action,即每天 500,000 次查询,平均约 5.8 QPS。按 20 倍突发估算约 120 QPS,还未计入并行 blob 读取。若 10% 的 action 产生新的 300 MB 输出,未压缩入口约为每天 1.5 TB;压缩和去重会降低实际存储,但应预留多 TB 对象容量和带宽余量。垃圾回收从仍存活的 action 结果和 manifest 出发,沿摘要引用关系遍历,经过宽限期后再按大小配额、LRU 或年龄清理。可达 blob 不能删除,并要按租户公平执行配额。
5. 让故障和正确性可观测
区分缓存超时、权限失败、blob 缺失、摘要不匹配和后端不可用。缓存超时可以执行;持续故障需要准入控制、本地缓存优先和有限重试,避免 CI 重试风暴。按仓库、action 类别、平台和工具链记录命中率、查询延迟、blob 带宽、上传中止、驱逐、损坏和租户拒绝。定期清空本地缓存做干净重建,比较执行日志或产物摘要,以发现不确定性和错误命中。
强回答示例
我会提供两个服务:认证的 action-cache 索引和不可变 CAS。action key 覆盖规范化命令、编译器与平台身份、声明输入 Merkle 根、白名单环境、flag 以及锁定的外部依赖。命中返回输出摘要,客户端校验并下载 blob 后再落盘。未命中进入沙箱执行,幂等上传并校验 blob,所有引用存在后才发布结果。action 元数据按租户和仓库复制,CAS 跨故障域分片或放入对象存储。缓存故障时可回退执行;写入只给可信 CI,并设置配额、加密和默认禁止跨租户复用。容量按约 120 峰值 QPS 和多 TB 存储规划,再验证命中率、摘要错误、不确定 action、并发写入、工具链升级、GC 可达性和故障恢复。
常见错误
- 只哈希源码,遗漏编译器、flag、平台、环境或锁定依赖。
- 把 action 结果和输出 blob 放进一个可变记录,导致部分发布。
- 不检查命名空间授权就跨租户复用摘要。
- 把缓存不可用设计成构建全停,而不是受控未命中和回退。
- 使用一个全局 LRU,未追踪存活 action 结果引用就删除 blob。
- 把 stdout 或 stderr 数量当成命中率,应该记录执行策略和明确的命中计数。
- 没有干净构建、跨机器复现和不确定 action 测试就声称命中率很高。
追问与回答
编译器工具链升级,但命令行没有变化怎么办?
工具链身份必须进入 action key,通常使用固定摘要或版本化执行镜像。迁移时可以双读旧命名空间用于回滚,但写入应进入新命名空间并单独统计未命中。不能只因源码和 flag 相同就复用旧产物。
缓存被投毒后如何恢复?
停止不可信写入,隔离受影响命名空间,并根据审计日志定位错误 action 结果及其可达 blob。失效 action 索引,用可信执行重建产物,再重新填充。恢复期间缓存应保持可选,并保留证据供事故复盘。
action 在执行时下载未锁定依赖怎么办?
这是非 hermetic action:在依赖固定且下载字节纳入输入闭包前标记为不可缓存。紧急情况下可以设置仓库范围和短 TTL 的明确例外,但不能把它当作确定性复用。
如何从本地缓存迁移且不让 CI 中断?
先以 shadow 模式读取远程缓存,比较本地和远程 key 及输出摘要,再让少量仓库启用远程命中。保留本地执行和本地缓存回退,写入仅开放给可信 CI,命中率、延迟和错误命中检查通过后逐步扩大。