[英]Rails / Postgres Lookup Performance
我有一个状态仪表板,显示每分钟“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}
我的控制器/查找代码如下:
class SensorsController < ApplicationController
def index
@sensors = Sensor.all
end
end
我曾想过将一个latest_log_id
引用放到Sensor
,然后每次发布该传感器的新日志时都会更新它 - 但我头脑中的一些东西告诉我其他开发人员会说这是一件坏事。 是这样的吗?
这样的问题通常如何解决?
有两种相对简单的方法可以做到这一点:
基本的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的评论 - 您实质上是在询问是否应该构建缓存。 答案是'这取决于'。 缓存有许多优点和许多缺点,因此它归结为“价值成本的好处”。 根据您所描述的内容,您似乎并不熟悉它们引入的困难(Google“缓存失效”),或者它们是否适用于您的情况。 我建议反对它,直到你可以证明a)它在非缓存解决方案上增加了真正的价值,并且b)它可以安全地应用于你的场景。
有3种选择:
-
由PinnyM解释
您可以从Sensor连接到每行的最新日志记录,以便在一个查询中获取所有内容。 不确定你的行数如何与你拥有的行数相比,可能它仍然比你想要的慢。
你提到的事情 - 缓存latest_log_id
(甚至只缓存latest_status
如果这是仪表板所需的全部内容)实际上是可以的。 它被称为非规范化 ,如果仔细使用它是一个有用的东西。 为了能够优化读取性能,您可能会遇到同样存在的“计数器缓存”插件 - 复制数据。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.