Rails中的请求超时

时间:2015-03-27 03:33:11

标签: ruby-on-rails ruby performance postgresql

我们正在研究数据可视化问题。我们的客户希望我们在图表上显示蜜蜂蜂巢的最近6个月数据。

显然,它将成为一个庞大的数据集。添加索引我们克服了加载数据时数据库缓慢的问题,尽管我们在图形上可视化数据时仍有问题。

以下是相关代码:

def self.prepare_single_hive_messages_for_datatable_dygraph(messages, us_metric_enabled)
  data = []
  messages.each do |message|
    record = []
    record << message.occurance_time.to_s(:dygraph_format)
    record << weight_according_to_metric(message.weight, us_metric_enabled)
    record << temperature_according_to_metric(message.temperature, us_metric_enabled)
    record << (message.humidity.nil? ? nil : message.humidity.to_f)
    data << record
  end
  return data
end

问题是messages.each非常慢并且需要超过30秒。有没有解决方法可以解决这个问题?

项目规格:

  • Rails版本:4.1.9
  • 图表库:Dygraph
  • 数据库:Postgres

2 个答案:

答案 0 :(得分:1)

有两种方法可以解决像这样的性能问题。

  1. 查找并纠正性能瓶颈
  2. 将其分成小块
  3. 查找效果问题

    首先,获取足够大的数据集以重现dev系统上的问题设置。然后查看日志,以便查看事务处理的时间。你应该寻找这样一条线:

    在432.1ms完成200 OK(浏览次数:367.7ms | ActiveRecord:61.4ms)

    重新运行任务几次,因为缓存可能会导致变化。写下你不同的时间。然后删除循环中的所有内容并仅使用循环运行它。这些数字是否看起来合情合理?如果是这种情况,那么您就知道问题是您在循环中所做的工作。接下来,将循环中的每一行单独添加(如果它们彼此依赖,则一次添加一行)。弄清楚哪一行会导致这些数字跳得最多。

    您应该尝试对代码进行性能调整。检查可能更智能的查询。确保您不会一遍又一遍地查询相同的数据。如果你在模型中有一个函数来计算某些东西而你多次调用它来得到相同的答案,那么使用它只计算一次:

    def something
         return @savedvalue if @savedvalue
         @savedvalue = really complex calculation
    end
    

    目标是找到更糟糕的罪犯,这样你才能做出影响最大的变化。但是,如果您正在使用大量数据,这可能只会让您到目前为止。对所有数据进行足够的性能调整可能是不可能的。在这种情况下,有选项2。

    将其分成小块

    • 编写第二个rails操作,他唯一的工作就是在图表上呈现单个记录。它将执行循环的内部部分,但仅限于传递给它的id的消息。

    • 调用原始函数来设置视图并将消息列表传递给视图。在视图循环中通过消息列表来设置jquery ajax代码,为每条消息调用一次上面的动作。让这个文件准备就绪。

    然后,页面将加载一个空图表...但是一旦启动,单个处理过的记录将被输入到它并在页面上一次显示一个。它仍然需要长时间(或者甚至更长一点因为开销)来完成图表...但它将不再超时。每个ajax调用都会自动快速命中服务器而不是一个很长的命中。

    我刚刚使用这种技术在我工作的网站上加载了一个相当长的报告。理想情况下,我们希望解决任何潜在的性能问题......但我们真正想要的是立即处理报告,然后像我们有时间一样解决性能问题。

答案 1 :(得分:1)

好的,你说每个人都看到同一组数据,这很好,意味着我们可以缓存而不用担心谁登录,首先是你的方法,只需要很小的改进

def self.prepare_single_hive_messages_for_datatable_dygraph(messages, us_metric_enabled)
  messages.inject([]) do |records, message|
    records << [].tap do |record|
      record << message.occurance_time.to_s(:dygraph_format)
      record << weight_according_to_metric(message.weight, us_metric_enabled)
      record << temperature_according_to_metric(message.temperature, us_metric_enabled)
      record << (message.humidity.nil? ? nil : message.humidity.to_f)
    end
  end
end

然后创建一个缓存函数,运行此方法并缓存它

# some class constants
CACHE_KEY = 'some_cache_key'
EXPIRY_TIME = 15.minutes

# the methods
def self.write_single_hive_messages_to_cache(messages, us_metric_enabled)
  Rails.cache.write CACHE_KEY,
    self.class.prepare_single_hive_messages_for_datatable_dygraph(messages, us_metric_enabled),
    expires_in: EXPIRY_TIME
end

一个简单的缓存读取方法

self.read_single_hive_messages_from_cache
  Rails.cache.read CACHE_KEY
end

然后创建一个rake任务,只获取这些消息并调用缓存方法,rails将写入缓存。

创建一个调用此rake任务的cron作业,将cron作业设置为5分钟左右,到期时间更长以防万一由于某些原因cron作业没有运行,数据仍然可用下一次运行。

这样您的处理在后台运行,每隔5(或您选择的任何时间)分钟,页面加载应该正常发生而没有任何延迟,因为阵列数据将从预先计算的缓存加载。

如果cron停止工作,数据将在我设置的15分钟内到期,然后读取缓存方法将返回nil,您可以避免这种情况并将数据设置为永不过期,但随后数据将变得陈旧,旧数据将继续返回。

处理这个问题的另一种方法是告诉缓存读取方法如何自己生成缓存,所以如果它找到缓存为空它会生成一个并在返回数据之前自己缓存它,该方法看起来像这样

def self.read_single_hive_messages_from_cache(messages, us_metric_enabled)
  Rails.cache.fetch CACHE_KEY, expires_in: EXPIRY_TIME do
    self.class.write_single_hive_messages_to_cache(messages, us_metric_enabled)
  end
end

但请确保messagesActiveRecord::Relation而不是已处理的数组,因为您不想查询超过100万条记录,然后找到已准备好的缓存,如果它是ActiveRecord::Relation它将不会触及数据库,直到数组启动(在缓存块内),如果缓存存在,它将在你进入块之前返回,因此数据赢得了获取,节省了大量的查询。

我知道答案很长,如果你需要更多的帮助告诉我。