陈旧缓存需要有界策略
陈旧响应可以在源站故障时维持公开手册的可读性,也可能保留过时价格、已撤销权限或误导性的状态。可用性并不天然高于正确性。启用这种行为之前,应先确定哪些信息允许继续使用、什么条件下可以复用,以及最多能旧多久,而不是看到一次错误响应就无条件拿旧成功结果代替。 用可用性换新鲜度 过期和淘汰是两件事。过期响应仍可留在缓存里,用于条件验证或受到许可的陈旧响应;但冷缓存或已经淘汰条目的缓存,没有旧正文可返回。陈旧指令无法凭空创造副本,保存着字节也不会自动授予复用权限。选中的响应还必须匹配当前请求,包括相关 Vary 维度和访问边界,不能拿另一个请求留下的成功内容填补故障。 如何表示 stale-while-revalidate 允许在有界窗口内返回旧响应,同时尝试不阻塞该响应的验证。stale-if-error 则允许在尝试访问源站遭遇符合条件的错误时使用旧响应。两者触发条件不同,都不强制缓存保存内容,也不能保证每种缓存实现都支持。请求到达时若已越过允许窗口,不能因为之前有过一次许可,就继续无限期使用旧内容。 对于 max-age=30, stale-if-error=60,额外六十秒从新鲜期结束后开始计算,不是从每一次失败尝试重新开始。候选响应的总年龄窗口约为九十秒。RFC 5861 指定了会形成 500、502、503、504 的错误场景。认证失败或资源不存在,不能随手重新包装成可用性故障,再用旧的成功响应掩盖真正需要客户端看到的结果。 成功的条件验证不一定重新下载正文。三零四响应可以依据验证元数据更新存储响应,同时保留原来的表示字节。应把这种结果与替换正文的二百响应、故障期间的陈旧命中分别记录;三种情况可能显示相同页面,却对应不同的新鲜度依据,不能仅靠正文是否改变判断验证是否发生。 坏掉的样子 每次重验证失败后都把 Age 清零,会把有界例外变成无限陈旧。保留旧正文却刷新存储时间戳,也会造成同类错误。失败时应保留年龄;只有真正的新响应或成功验证,才提供更新新鲜度的依据。后台验证通常由流量触发,所以一个长期无人访问的缓存,不一定会在下一次用户访问之前主动把内容更新好。 只读或幂等并不代表可以安全返回旧内容。一个 GET 也可能涉及对时效敏感的权限或价格。RFC 9111 禁止在适用的 no-cache、must-revalidate 等指令不允许时复用陈旧响应。应避免互相矛盾的策略,并测试实际部署的缓存如何处理这些限制。另外,RFC 5861 中关于旧 Warning 字段的建议,已被废弃该字段的 RFC 9111 更新,不能把它当作当前验收必需项。 观察指标中要区分陈旧响应和新鲜命中,记录总年龄、错误类别以及验证是否成功,同时避免记录秘密信息。如果每次都返回旧内容,漂亮的 HTTP 成功率可能掩盖持续故障的源站。恢复验收必须确认发生了真实替换或成功验证,而不是只看到源站进程重新启动,就宣布数据已经恢复新鲜。 快速验证 准备 Node.js 与 npm。本轮使用 Node 24.19.0 和已有的 http-cache-semantics 4.2.0 安装运行了 JavaScript。安装固定版本后,用 NODE_PATH="$stale_lab/node_modules" node 执行 JavaScript。这是使用虚构响应和受控时钟的确定性策略与正文选择实验,不发送请求,也不制造真实宕机;只有安装阶段需要联网。 ...