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;如果需要保持最新 → 使用基于键的失效机制
免责声明:本文内容来自互联网,该文观点不代表本站观点。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,请到页面底部单击反馈,一经查实,本站将立刻删除。