我有一个状态仪表板,显示每分钟“ping”应用程序并记录其状态的远程硬件设备的状态。
class Sensor < ActiveRecord::Base
has_many :logs
def most_recent_log
logs.order("id DESC").first
end
end
class Log < ActiveRecord::Base
belongs_to :sensor
end
鉴于我只对显示当前状态感兴趣,仪表板只显示所有传感器的最新日志。该应用程序已经运行了很长时间,并且有数千万条 Log
记录。
我遇到的问题是仪表板需要大约 8 秒才能加载。据我所知,这主要是因为有一个 N+1 查询在获取这些日志。
Completed 200 OK in 4729.5ms (Views: 4246.3ms | ActiveRecord: 480.5ms)
我确实有以下索引:
add_index "logs", ["sensor_id", "id"], :name => "index_logs_on_sensor_id_and_id", :order => {"id"=>:desc}
我的 Controller /查找代码如下:
class SensorsController < ApplicationController
def index
@sensors = Sensor.all
end
end
- 如何使加载时间合理?
- 有没有办法避免 N+1 并重新加载它?
我曾想过将 latest_log_id
引用放在 Sensor
上,然后在每次发布该传感器的新日志时更新它 - 但我脑子里的东西告诉我认为其他开发人员会说这是一件坏事。是这样吗?
通常如何解决此类问题?
最佳答案
有两种相对简单的方法可以做到这一点:
- 使用 ActiveRecord eager loading 只提取最新的日志
- 为此目的推出您自己的迷你预加载系统(作为哈希)
基本的 ActiveRecord 方法:
subquery = Log.group(:sensor_id).select("MAX('id')")
@sensors = Sensor.eager_load(:logs).where(logs: {id: subquery}).all
请注意,您不应为每个传感器使用most_recent_log
方法(这将触发 N+1),而应使用 logs.first
。 logs
集合中实际上只会预取每个传感器的最新日志。
从 SQL 的角度来看,自己滚动可能更有效,但阅读和使用起来更复杂:
@sensors = Sensor.all
logs = Log.where(id: Log.group(:sensor_id).select("MAX('id')"))
@sensor_logs = logs.each_with_object({}){|log, hash|
hash[log.sensor_id] = log
}
@sensor_logs
是一个 Hash,允许通过 sensor.id
快速查找最新日志。
关于您关于存储最新日志 ID 的评论 - 您实质上是在询问是否应该构建缓存。答案是“视情况而定”。缓存有很多优点也有很多缺点,因此归结为“ yield 是否值得付出代价”。根据您的描述,您似乎并不熟悉它们引入的困难(谷歌“缓存失效”)或者它们是否适用于您的情况。在您可以证明 a) 它比非缓存解决方案增加了真正的值(value),并且 b) 它可以安全地应用于您的场景之前,我建议不要使用它。
关于ruby-on-rails - Rails/Postgres 查找性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36672481/