我有一个状态信息中心,可显示远程硬件设备的状态。应用程序每分钟并记录其状态。
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}
我的控制器/查找代码如下:
class SensorsController < ApplicationController
def index
@sensors = Sensor.all
end
end
我曾考虑将latest_log_id
引用放到Sensor
上,然后每次发布该传感器的新日志时更新它 - 但我头脑中的某些东西告诉我其他开发人员会说这是一件坏事。是这种情况吗?
这样的问题通常如何解决?
答案 0 :(得分:2)
有两种相对简单的方法可以做到这一点:
基本的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
是一个哈希,允许sensor.id
快速查找最新的日志。
关于存储最新日志ID的评论 - 您实际上是在询问是否应该构建缓存。答案是&#39;它取决于&#39;。缓存有许多优点和许多缺点,因此它归结为价值成本&#39;。根据您的描述,您并不熟悉他们所介绍的困难(Google&#39;缓存失效&#39;)或者它们是否适用于您的情况。我建议反对它,直到你能证明a)它在非缓存解决方案上增加了真正的价值,并且b)它可以安全地应用于你的场景。
答案 1 :(得分:1)
有3种选择:
-
由PinnyM
您可以从Sensor连接到每行的最新日志记录,以便在一个查询中获取所有内容。不确定你的行数如何与你所拥有的行数有关,可能它仍然比你想要的慢。
你提到的事情 - 缓存latest_log_id
(如果仪表板需要的话,甚至只缓存latest_status
)实际上是可以的。它被称为denormalization,如果仔细使用它会很有用。为了能够优化读取性能,您可能会遇到同样存在的“计数器缓存”插件 - 复制数据。