如何优化/缓存Rails查询



我有一个Rails应用程序,它的查询时间太长。它使用postgresqlDB,查询由一个表组成,该表包含数千条记录。

统计_控制器.rb

all_data = Usagedata.select([:start_time, :end_time, :node_count, :processors, :id, :wall_duration, :local_user_id]) 
    .where(Usagedata.arel_table[(:wall_duration)].not_eq("0"))
    .in_range( @from_date, @to_date)
if @user
  all_data = all_data.by_user(@user)
end
all_data = all_data.to_a #Forcing to make query 
@data = all_data = all_data.to_a 

我想做的是将main查询结果(没有in_range和用户语句)保存在Rails服务器端应用程序缓存中,并每小时更新一次数据。

应该缓存的部分代码

Usagedata.select([:start_time, :end_time, :node_count, :processors, :id, :wall_duration, :local_user_id]) 
    .where(Usagedata.arel_table[(:wall_duration)].not_eq("0"))

缓存记录的使用

添加到该客户端可以从日历@from_date和@to_date中选择日期范围。日期之间的时间段可以从1天开始~3年。(这就是为什么缓存应该存储DB表中的所有记录。)数据用于绘制图表和显示/计算顶级用户统计数据。

我试过@MrTheWalrus解决方案

@statistics = Rails.cache.fetch('usagedata', :expires_in => 24.hours) do
  Usagedata.select([:start_time, :end_time, :node_count, :processors, :id, :wall_duration, :local_user_id])
    .where(Usagedata.arel_table[(:wall_duration)].not_eq("0")).all
end

但通过这种方式,我无法让我的子查询工作:

all_data = @statistics.in_range( @from_date, @to_date)
if @user
  all_data = all_data.by_user(@user)
end

这给了我一个错误:

undefined method `in_range' for #<Array:0x007fa5ecc77588>

尽管我在Usagedata模型中定义了in_range,如下所示:

def self.in_range(from_date, to_date)
  where("start_time <= :to AND end_time >= :from", :from => from_date, :to => to_date)
end

我做错了什么

编辑:感谢@Craig Ringer解决方案,我成功解决了此处描述的索引问题:

整个应用程序似乎真的很慢。我做错了什么?也许我也需要添加索引,但是怎么添加呢?

  Usagedata Load (243.4ms)  SELECT start_time, end_time, node_count, processors, id, wall_duration, local_user_id FROM "usagedata" WHERE ("usagedata"."wall_duration" != 0) AND (start_time <= '2013-09-02 20:59:59.999999' AND end_time >= '2013-05-05 21:00:00.000000')EXPLAIN (1.9ms)  EXPLAIN SELECT start_time, end_time, node_count, processors, id, wall_duration, local_user_id FROM "usagedata" WHERE ("usagedata"."wall_duration" != 0) AND (start_time <= '2013-09-02 20:59:59.999999' AND end_time >= '2013-05-05 21:00:00.000000')
EXPLAIN for: SELECT start_time, end_time, node_count, processors, id, wall_duration, local_user_id FROM "usagedata"  WHERE ("usagedata"."wall_duration" != 0) AND (start_time <= '2013-09-02 20:59:59.999999' AND end_time >= '2013-05-05 21:00:00.000000')
QUERY PLAN
 ---------------------------------------------------------------------------------------
 Seq Scan on usagedata  (cost=0.00..4558.02 rows=7989 width=34)
 Filter: ((wall_duration <> 0) AND (start_time <= '2013-09-02 20:59:59.999999'::timestamp without time zone) AND (end_time >= '2013-05-05 21:00:00'::timestamp without time zone))
 (2 rows)

Craig Ringer的评论已经讨论了索引,所以我只想谈谈缓存。

包含的缓存代码的问题在于,缓存的是一个ActiveRecord::Relation——基本上只是一个等待运行的SQL查询,而不是该查询的结果。缓存关系意味着每次从缓存加载关系时,它仍然必须执行查询,这是需要很长时间的部分。在末尾附加一个.all以强制查询实际运行-这将确保缓存结果,而不是查询:

@statistics = Rails.cache.fetch('usagedata', :expires_in => 24.hours) do
  Usagedata.select([:start_time, :end_time, :node_count, :processors, :id, :wall_duration, :local_user_id]).
    where(Usagedata.arel_table[(:wall_duration)].not_eq("0")).all
end

编辑:不能对此调用.in_range的原因是.in_range修改了查询(通过添加WHERE子句)。一旦您运行了查询并缓存了结果,就不能以这种方式修改它——缓存查询结果的全部目的是运行一次查询并多次使用结果——如果查询发生更改,那就不是一个选项。

假设添加索引还没有解决您的问题,我的建议是在Ruby中过滤结果,而不是在数据库中。假设您已经填充了缓存(通过Where或其他方式):

from_time = 1.week.ago
to_time = 1.day.ago
@statistics = Rails.cache.fetch('usagedata')
@filtered_statistics = @statistics.select do |item|
  item.start_time < to_time && item.end_time > from_time
end

在(start_timeend_time)上使用带有WHERE ("usagedata"."wall_duration" != 0)索引筛选子句的部分索引将使此查询更快。甚至是CCD_ 9上的非部分索引。

这可能会使客户端缓存变得不必要。如果没有,请查看Rails是否支持创建和管理服务器端物化视图。

最新更新