Rails / Postgres查找性能

时间:2016-04-17 03:51:40

标签: ruby-on-rails ruby database performance postgresql

我有一个状态信息中心,可显示远程硬件设备的状态。应用程序每分钟并记录其状态。

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
  1. 如何使加载时间合理?
  2. 有没有办法避免N + 1并重新加载?
  3. 我曾考虑将latest_log_id引用放到Sensor上,然后每次发布该传感器的新日志时更新它 - 但我头脑中的某些东西告诉我其他开发人员会说这是一件坏事。是这种情况吗?

    这样的问题通常如何解决?

2 个答案:

答案 0 :(得分:2)

有两种相对简单的方法可以做到这一点:

  • 使用ActiveRecord预先加载来提取最新日志
  • 为此目的滚动您自己的迷你渴望加载系统(作为哈希)

基本的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种选择:

  1. 急切加载
  2. 接合
  3. 缓存当前状态
  4. -

    1. 由PinnyM

    2. 解释
    3. 您可以从Sensor连接到每行的最新日志记录,以便在一个查询中获取所有内容。不确定你的行数如何与你所拥有的行数有关,可能它仍然比你想要的慢。

    4. 你提到的事情 - 缓存latest_log_id(如果仪表板需要的话,甚至只缓存latest_status)实际上是可以的。它被称为denormalization,如果仔细使用它会很有用。为了能够优化读取性能,您可能会遇到同样存在的“计数器缓存”插件 - 复制数据。