WordPress 6.9 缓存优化:缓存查询结果的键不再使用最后修改时间作为盐值!
WordPress 之前查询结果缓存是把分组的「最后修改时间戳」作为一种盐值(salt),这意味着当该值发生变化时,缓存键也会改变,从而导致创建新的缓存。这种缓存失效方法多年来被证明是有效的,但也存在一个明显缺点——它会生成过多的缓存。
试想一个高流量网站,其内容不断更新,每10分钟就有新文章发布。每次创建新文章或更新文章时,「最后修改」缓存都会被更新。因此,首页的 WP_Query查询每次在文章更新时都可能生成一个新的缓存。这可能导致创建数百甚至数千个缓存。对象缓存的设计初衷是让未使用的键过期并从缓存中移除,然而,这个过程并不迅速,这些键可能需要数小时甚至数天才能自然地从缓存中淘汰。此外,还存在对象缓存膨胀并导致正在使用的缓存被意外移除的风险。
不再将「最后修改时间戳」用作盐值
解决方案在于重用缓存键:不再将「最后修改时间戳」用作盐值,而是将其存储在缓存值内部。检索缓存值时,系统会将存储在缓存中的「最后修改」值与实际的「最后修改」值进行比较。如果两者不匹配,则将缓存标记为无效,从数据库中重新获取值,并使用更新后的「最后修改」值重新保存缓存。
这些变更与现有的持久缓存插件实现兼容(包括 WPJAM Basic 实现的 object-cache.php)。插件开发者无需修改代码即可在 WordPress 6.9 中支持这些功能。与其他缓存函数一样,这些函数也是可插拔的,插件开发者可以根据自身特定的缓存实现进行优化:
wp_cache_get_salted( string $cache_key, string $group, string|string[] $salt ): mixed
wp_cache_set_salted( string $cache_key, mixed $data, string $group, string|string[] $salt, int $expire = 0 ): bool
wp_cache_get_multiple_salted( string[] $cache_keys, string $group, string|string[] $salt ): mixed[]
wp_cache_set_multiple_salted( mixed[] $data, string $group, string|string[] $salt, int $expire = 0 ): bool[]
行为变更对比
WordPress 6.8 及之前版本:
- 文章更新 → 更新 posts 表最后修改时间
- WP_Query 调用 → 使用包含最后修改时间的键来缓存数据库查询
- 另外一篇文章更新 → 再次更新 posts 表最后修改时间
- 相同 WP_Query 调用 → 旧缓存键失效 → 强制重新查询,并使用包含更新后的最后修改时间的新键来缓存数据库查询
WordPress 6.9 新机制:
- 文章更新 → 更新 posts 表最后修改时间
- WP_Query 调用 → 缓存数据库查询结果及其最后修改时间
- 另外一篇文章更新 → 再次更新 posts 表最后修改时间
- 相同 WP_Query 调用 → 命中现有缓存 → 在内存中比较最后更改时间 → 将之前生成的缓存替换为新的结果
虽然两种操作都会执行两次缓存查找,但新方法会重用现有的缓存键,并在内存中比较最后修改时间,这样可以防止缓存中生成大量无法访问的缓存键。
上面我们举的例子是文章查询结果的缓存行为变动,同样的行为变化也适用于其他查询类,例如分类查询、评论查询、用户查询等。
直接检查 / 设置查询的缓存
如果你是插件开发者,那么在使用新 wp_cache_*_salted 函数,在传入盐值数组的时候,数组中的元素顺序必须保持一致,所以一定要检查核心代码中的盐值数组中元素顺序,然后使用相同的顺序以确保缓存命中。
此外如果需要支持多个 WordPress 版本,可以使用类似如下的代码:
if (function_exists( 'wp_cache_get_salted` ) ){
$data = wp_cache_get_salted( $cache_key, 'comment-queries', $last_changed );
} else {
// your current code here
}
升级到 WordPress 6.9
升级后可能出现短期缓存命中率下降,建议主动做一次全量清理,实现清除旧的缓存键,以防止过期的缓存条目长期存在,从而避免因缓存策略而导致不必要的缓存清除
总结一下,WordPress 查询结果缓存机制不再将「最后修改时间戳」用作盐值,而是将其存储在缓存值内部,这样的优化有几个好处:
- 缓存键被重用,确保了缓存查询数量的稳定,并且缓存查询能够自行清理。
- 可预测的缓存键使得缓存能够被高效地使用,比如缓存插件可以识别页面上的查询类型,并在单次请求中预先请求多个缓存键。
