进一步改进Active Record / Postgresql查询

时间:2017-11-24 16:31:58

标签: ruby-on-rails ruby postgresql activerecord

跟进我的问题here,我试图进一步改进搜索。我们首先搜索重播表(搜索2k记录),然后获得与该表相关联的唯一玩家(每个10个,所以20k记录)并渲染JSON。这是通过控制器完成的,搜索内容如下:

def index
 @replays = Replay.includes(:players).where(map_id: params['map_id'].to_i).order(id: :desc).limit(2000)
 render json: @replays[0..2000].to_json(include: [:players])
end 

表现:

Completed 200 OK in 254032ms (Views: 34.1ms | ActiveRecord: 20682.4ms)

实际的Active Record搜索读作:

Replay Load (80.4ms)  SELECT  "replays".* FROM "replays" WHERE "replays"."map_id" = $1 ORDER BY "replays"."id" DESC LIMIT $2  [["map_id", 1], ["LIMIT", 2000]]
Player Load (20602.0ms)  SELECT "players".* FROM "players" WHERE "players"."replay_id" IN (117217...

这大部分都有效,但仍需要很长时间。有没有办法提高绩效?

2 个答案:

答案 0 :(得分:0)

我知道find_each可以用于批处理查询,这可能会减轻这里的内存负担。你可以尝试以下内容,看看它对时间的影响吗?

Replay.where(map_id: params['map_id'].to_i).includes(:players).find_each(batch_size: 100).map do |replay|
  replay.to_json(includes: :players)
end

我不确定这会有效。可能是映射否定了批处理的好处 - 肯定有更多的查询,但它会使用更少的内存,因为它不需要存储>一次20k记录。

玩游戏并看看它的样子 - 也可以调整批量大小,看看它是如何影响事物的。

有一点需要注意,你不能申请限制,所以请记住这一点。

我相信其他人会想出一个更加流畅的解决方案,但希望在此期间这可能会有所帮助。如果检查速度很糟糕,请告诉我,我会删除这个答案:)

答案 1 :(得分:0)

你被这个问题https://postgres.cz/wiki/PostgreSQL_SQL_Tricks_I#Predicate_IN_optimalization

所困扰
  

当值列表长于八十个数字时,我发现了关于IN谓词的最优化可能性的注释pg_performance。对于更长的列表,最好使用多值创建常量子查询:

     

SELECT *      FROM标签     WHERE x IN(1,2,3,... n); - n> 70

     

- 更快的情况   选择 *      FROM标签     在哪里x IN(VALUES(10),(20));

     

对于较大数量的项目,使用VALUES会更快,因此请勿将其用于较小的值集。

基本上,SELECT * FROM WHERE IN ((1),(2)...)具有很长的值列表非常慢。如果您可以将其转换为值列表(例如SELECT * FROM WHERE IN (VALUES(1),(2) ...)

),则速度会快得多

不幸的是,由于这是在活动记录中发生的,因此对查询进行控制有点棘手。您可以避免使用includes调用,只需手动构造SQL以加载所有子记录,然后手动建立关联。

或者,您可以使用补丁活动记录。这是我在rails 4.2中在初始化程序中所做的。

module PreloaderPerformance
  private
  def query_scope(ids)
    if ids.count > 100
      type = klass.columns_hash[association_key_name.to_s].sql_type
      values_list = ids.map do |id|
        if id.kind_of?(Integer)
          " (#{id})"
        elsif type == "uuid"
          " ('#{id.to_s}'::uuid)"
        else
          " ('#{id.to_s}')"
        end
      end.join(",")

      scope.where("#{association_key_name} in (VALUES #{values_list})")
    else
      super
    end
  end
end

module ActiveRecord
  module Associations
    class Preloader
      class Association #:nodoc:
        prepend PreloaderPerformance
      end
    end
  end
end

这样做我看到我的一些查询速度提高了50倍,而且还没有任何问题。注意它没有经过完全的战斗测试,我敢打赌如果你的关联是使用foreign_key关系的唯一数据类型会有一些问题。在我的数据库中,我只使用uuids或整数来表示我们的关联。有关猴子修补核心轨道行为的常见警告适用。