【问题标题】:Creating thousands of records in Rails在 Rails 中创建数千条记录
【发布时间】:2011-01-06 07:12:35
【问题描述】:

让我做个准备:我的应用程序处理礼品卡。当我们创建卡片时,它们必须有一个唯一的字符串,用户可以使用它来兑换它。因此,当有人像零售商一样订购我们的礼品卡时,我们需要制作大量新的卡片对象并将它们存储在数据库中。

考虑到这一点,我想看看我的应用程序生成 100,000 张卡片的速度有多快。数据库专家,我不是,所以我需要有人解释一下这个小现象:当我创建 1000 张卡片时,需要 5 秒。当我创建 100,000 张卡片时,应该需要 500 秒吧?

现在我知道你想看到什么,我正在使用的卡片创建方法,因为第一个假设是它会越来越慢,因为它正在检查一堆卡片的唯一性,随着它的进行越来越多。但我可以告诉你我的 rake 任务

desc "Creates cards for a retailer"
task :order_cards, [:number_of_cards, :value, :retailer_name] => :environment do |t, args|
  t = Time.now
  puts "Searching for retailer"
  @retailer = Retailer.find_by_name(args[:retailer_name])
  puts "Retailer found"
  puts "Generating codes"
  value = args[:value].to_i
  number_of_cards = args[:number_of_cards].to_i
  codes = []
  top_off_codes(codes, number_of_cards)
  while codes != codes.uniq
    codes.uniq!
    top_off_codes(codes, number_of_cards)
  end
  stored_codes = Card.all.collect do |c|
    c.code
  end
  while codes != (codes - stored_codes)
    codes -= stored_codes
    top_off_codes(codes, number_of_cards)
  end
  puts "Codes are unique and generated"
  puts "Creating bundle"
  @bundle = @retailer.bundles.create!(:value => value)
  puts "Bundle created"
  puts "Creating cards"
  @bundle.transaction do
    codes.each do |code|
      @bundle.cards.create!(:code => code)
    end
  end
  puts "Cards generated in #{Time.now - t}s"
end

def top_off_codes(codes, intended_number)
  (intended_number - codes.size).times do
    codes << ReadableRandom.get(CODE_LENGTH)
  end
end

我正在使用一个名为 readable_random 的 gem 作为唯一代码。因此,如果您通读所有这些代码,您会发现它在开始创建卡片之前已经完成了所有的唯一性测试。它还在运行时将状态更新写入屏幕,并且它总是在创建时坐一会儿。同时,它通过了唯一性测试。所以我对 stackoverflow 社区的问题是:为什么我的数据库随着我添加更多卡片而变慢?为什么这不是关于每张卡时间的线性函数?我确信答案很简单,我只是一个对数据存储一无所知的白痴。如果有人有任何建议,您将如何优化此方法,您认为创建 100,000 张卡片的速度有多快?

(当我在图表上绘制我的时间并快速拟合曲线以获得我的直线公式时,我计算了使用我当前的代码创建 100,000 张卡片需要多长时间,结果显示为 5.5 小时。这可能完全错误,我不确定。但如果它保持在我拟合的曲线上,它就在附近。)

【问题讨论】:

  • 你试过没有交易吗?
  • 在我知道交易存在之前,我只是在没有交易块的情况下创建它们。交易加快了进程。

标签: ruby database optimization activerecord ruby-on-rails-3


【解决方案1】:

不是对您问题的回答,而是关于如何加快插入速度的一些建议:

  • 使用 Ruby 的 Hash 消除重复项 - 使用您的卡代码作为哈希键,将它们添加到哈希中,直到您的哈希增长到所需的大小。您也可以改用 Set 类(但我怀疑它是否比 Hash 快)。
  • 在数据库中使用批量插入,而不是一系列 INSERT 查询。大多数 DBMS 提供了以下可能性:使用新记录创建文本文件,并告诉数据库将其导入。这是MySQLPostgreSQL 的链接。

【讨论】:

  • 我试图实现您的哈希建议,但遇到了一些麻烦。我在生成代码时理解了唯一性测试。但是我将如何针对数据库中已经存在的卡片测试唯一性?另外,我正在尝试实现您的导入想法,并且正在使用我发现的名为 activerecord-import 的 gem。所以我会在一分钟内给它一个测试,看看是否有帮助。
  • 我最终没有使用哈希,因为我在上一条评论中提到的原因,但我使用 activerecord-import 进行了一些运行,以及一些新添加的索引,我得到了我的10万的时间从一天多下来到5.5分钟!非常感谢您的帮助!
  • 是的,唯一性可能很难完全确保。您可以尝试代替readable_random 的另一件事是UUID - 它们往往是唯一的,因为它们包括(但不披露)创建时间戳以及随机部分。我认为使用 UUID 可以将冲突视为例外,而不是规则。
  • 哦,还有一个想法:你可以完全跳过 Ruby/Rails 来执行这个任务,使用 PostgreSQL 中的存储过程。制作retailer_id 和desired_number_of_cards 输入参数,并在存储过程中循环创建输入记录。
  • 嘿,这是个好主意!我对数据库一无所知,但我一定会调查一下。我们将 Heroku 上的数据库升级到了他们的 Ronin 软件包,这将卡片创建速度加快了 100,000 到 3.5 分钟。但我肯定会研究 UUID 和 Postgres procs。感谢大家的帮助!
【解决方案2】:

我的第一个想法是围绕事务 - 如果您有 100,000 个待提交的更改等待在事务中提交,这会稍微减慢速度,但任何体面的数据库都应该能够处理。

你用的是什么数据库?

有哪些索引?

任何数据库优化,例如聚簇表/索引。

不确定 Ruby 事务支持 - @bundle.transaction 行是来自 ActiveModel 还是您正在使用的其他库?

【讨论】:

  • 我正在使用托管在 Heroku 上的 postgreSQL。除了默认索引外,我没有任何索引,但我一定会试一试并回复。
猜你喜欢
  • 1970-01-01
  • 2019-11-03
  • 1970-01-01
  • 2016-05-23
  • 2019-08-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-25
相关资源
最近更新 更多