【问题标题】:Updating Lots of Records at Once in Rails在 Rails 中一次更新大量记录
【发布时间】:2017-10-29 00:28:38
【问题描述】:

我有一个后台作业,我每 10 分钟运行大约 5,000 个。每个作业都会向外部 API 发出请求,然后在我的数据库中添加新记录或更新现有记录。每个 API 请求返回大约 100 个项目,因此每 10 分钟我会进行 50,000 个 CREATE 或 UPDATE sql 查询。

我现在处理这个问题的方式是,返回的每个 API 项都有一个唯一的 ID。我在我的数据库中搜索具有此 ID 的帖子,如果存在,它会更新模型。如果它不存在,它会创建一个新的。

想象一下 api 响应是这样的:

[
  {
    external_id: '123',
    text: 'blah blah',
    count: 450
  },
  {
    external_id: 'abc',
    text: 'something else',
    count: 393
  }
]

设置为变量collection

然后我在我的父模型中运行这段代码:

class ParentModel < ApplicationRecord
  def update
    collection.each do |attrs|
      child = ChildModel.find_or_initialize_by(external_id: attrs[:external_id], parent_model_id: self.id)
      child.assign_attributes attrs
      child.save if child.changed?
    end
  end
end

这些单独的调用中的每一个都非常快,但是当我在短时间内完成 50,000 次调用时,它确实会加起来并且会减慢速度。

我想知道是否有更有效的方法可以处理这个问题,我正在考虑做一些事情,比如:

class ParentModel < ApplicationRecord
  def update
    eager_loaded_children = ChildModel.where(parent_model_id: self.id).limit(100)
    collection.each do |attrs|
      cached_child = eager_loaded_children.select {|child| child.external_id == attrs[:external_id] }.first
      if cached_child
        cached_child.update_attributes attrs
      else
        ChildModel.create attrs
      end
    end
  end
end

基本上我会保存查找,而不是预先进行更大的查询(这也很快),但会在内存中进行权衡。但这似乎不会花太多时间,也许会稍微加快查找部分的速度,但我仍然需要进行 100 次更新和创建。

是否有某种方法可以进行我没有想到的批量更新?还有什么明显的东西可以让这个过程更快,或者减少我正在做的查询吗?

【问题讨论】:

    标签: ruby-on-rails performance postgresql optimization query-optimization


    【解决方案1】:

    你可以这样做:

    collection2 = collection.map { |c| [c[:external_id], c.except(:external_id)]}.to_h
    
    def update
      ChildModel.where(external_id: collection2.keys).each |cm| do
        ext_id = cm.external_id
        cm.assign_attributes collection2[ext_id]
        cm.save if cm.changed?
        collection2.delete(ext_id)
      end
      if collection2.present?
        new_ids = collection2.keys
        new = collection.select { |c| new_ids.include? c[:external_id] }
        ChildModel.create(new)
      end
    end
    

    更好,因为

    • 一次性获取所有需要的记录
    • 一次创建所有新记录

    如果不需要callbacks/validations,可以使用update_columns 唯一的缺点是更多的 ruby​​ 代码操作,我认为这是 db 查询的一个很好的权衡。

    【讨论】:

    • 啊,好吧,有道理。我绝对喜欢一次完成所有创作的想法。我唯一能预见到的问题是ChildModel.where(external_id: collection2.keys)collection2.keys 可能长达 100 个键,如果数组中有这么多键,这不是一个非常有效的查询吗?
    • 我不认为这应该是一个问题,我已经用where 查询获取了 1000 列。但是,如果该列没有被索引会很重要。如果它会更好你索引external_id
    猜你喜欢
    • 1970-01-01
    • 2013-09-26
    • 1970-01-01
    • 2015-07-31
    • 2014-05-12
    • 1970-01-01
    • 1970-01-01
    • 2018-05-28
    • 1970-01-01
    相关资源
    最近更新 更多