最快的查询是你从未执行的查询:Rails 缓存的四个层级

发布日期:2026-06-27 10:00:25   浏览量 :2
发布日期:2026-06-27 10:00:25  
2

Rails 性能:来自生产环境的经验教训 — #4

前三篇文章主要讨论如何降低查询成本——减少 N+1 查询问题、使用索引、避免将大量数据加载回 Ruby 中。这一篇则转换了视角:最快的查询是你从未执行过的查询。计算一次结果,将其存储起来,然后提供给所有后续请求。全文沿用同一个示例(一个 shipments 表),逐步讲解 Rails 缓存的四个层面:计算、失效、渲染、传输。

🔥 同样的 800 毫秒统计,为每位访客重新计算

首页显示了一个“快递员发货排名”。这个操作很耗时——扫描数百万条发货记录并执行 GROUP BY 需要 800 毫秒。

关键在于:这个数字对每个用户来说看起来都是一样的,而且它不需要是实时的(几分钟的数据延迟是可以接受的)。但是,我们的代码让每位访客都从头重新计算它——每次 800 毫秒。在流量高峰期,数据库会因为反复执行相同的计算而不堪重负。

这正是缓存的用武之地——开销大(值得保存)且被频繁命中(相同的结果被重复使用)。让我们从最基本的工具开始。

延续#1中提到的四个层面:前三篇文章优化的是“如何获取数据”;而缓存则是“不要重复计算已经计算过的内容”。贯穿全文的一个核心思想是——使用 updated_at 作为“版本号”,在数据未发生变化时复用结果。

📦 第一层(计算):Rails.cache.fetch

包裹那个 800 毫秒的计算过程:

Rails.cache.fetch("courier_ranking", expires_in: 5.minutes) do
  Shipment.group(:courier_id).count   # 耗时的计算
end

fetch 的作用,用一句话概括:如果存在,直接取用;如果不存在,则计算并存储。

  • 第一次访问:查找键 → 未命中 → 执行代码块(800 毫秒)→ 存储结果并设置 5 分钟过期时间 → 返回结果。
  • 接下来的 5 分钟内:查找键 → 命中 → 返回存储的值,代码块根本不会执行(仅需几毫秒)。

因此,第一位用户承担 800 毫秒的耗时,接下来 5 分钟内的所有其他用户都能免费获得结果。

两个关键参数:

  • "courier_ranking"):缓存的名称。如果结果随条件变化,键必须包含这些信息,例如 "courier_stats/#{courier.id}"
  • expires_in:过期时间,即“你能容忍多大的数据延迟”。如果需要更新的数据,就缩短这个时间。

什么情况下值得缓存?开销大——重新计算成本高,值得保存。② 被频繁命中——相同的结果被多次使用(跨用户,或同一查询被反复请求)。

至于“新鲜度”——这不是前提条件,而是一个需要解决的问题:如果能容忍一定的数据延迟 → 使用 expires_in;如果需要保持最新 → 使用基于键的失效机制

免责声明:本文内容来自互联网,该文观点不代表本站观点。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请到页面底部单击反馈,一经查实,本站将立刻删除。

关于我们
热门推荐
合作伙伴
免责声明:本站部分资讯来源于网络,如有侵权请及时联系客服,我们将尽快处理
Copyright © 2025-2027 ToB产业网址导航 公安备案 浙公网安备33010602013138号 浙ICP备16025413号-9
支持 反馈 关注 数据