在rails中更快地导入CSV数据

时间:2016-10-10 03:19:02

标签: ruby-on-rails

我正在构建一个导入模块,用于从csv文件导入大量订单。我有一个名为Order的模型,需要存储数据。

Order模型的简化版本在下面

sku
quantity
value
customer_email
order_date
status

导入数据时,必须发生两件事

  1. 需要清理任何日期或货币,即日期在csv中表示为字符串,这需要转换为Rails Date对象,并且需要通过删除任何逗号或美元符号将货币转换为小数
  2. 如果已存在行,则必须更新,基于两列检查唯一性。
  3. 目前我使用简单的csv导入代码

    CSV.foreach("orders.csv") do |row|
      order = Order.first_or_initialize(sku: row[0], customer_email: row[3])
      order.quantity = row[1]
      order.value= parse_currency(row[2])
      order.order_date = parse_date(row[4])
      order.status = row[5]
      order.save!
    end
    

    其中parse_currency和parse_date是用于从字符串中提取值的两个函数。在日期的情况下,它只是Date.strptime的包装器。

    我可以添加一个检查以查看记录是否已经存在,并且如果它已经存在则不执行任何操作并且应该节省一点时间。但我正在寻找速度更快的东西。目前导入大约100k行需要大约30分钟,空数据库。随着数据大小的增加,它会变慢。

    所以我基本上在寻找一种更快的方式来导入数据。

    任何帮助将不胜感激。

    修改

    根据这里的评论进行了一些测试后,我有一个观察和一个问题。我不确定他们是否应该去这里,或者我是否需要为这些问题打开一个新线程。所以,如果我不得不将其转移到另一个问题,请告诉我。

    我使用Postgres副本运行测试以从文件导入数据,并且花了不到一分钟。我刚刚将数据导入到新表中而没有任何验证。因此导入可以更快。

    Rails开销似乎来自2个地方

    1. 正在发生的多个数据库调用,即每行的first_or_initialize。这最终会变成多个SQL调用,因为它必须先找到记录,然后更新它然后保存它。
    2. 带宽。每次调用SQL服务器时,数据来回流动都需要花费很多时间
    3. 现在提出我的问题。如何将更新/创建逻辑移动到数据库,即如果基于sku和customer_email的订单已经存在,则需要更新记录,否则需要创建新记录。目前使用rails我使用first_or_initialize方法获取记录以防它存在并更新它,否则我创建一个新的并保存它。我如何在SQL中执行此操作。

      我可以使用ActiveRecord连接执行运行原始SQL查询,但我认为这不是一种非常优雅的方式。有没有更好的方法呢?

1 个答案:

答案 0 :(得分:2)

因为ruby 1.9 fastcsv现在是ruby核心的一部分。你不需要使用特殊的宝石。只需使用CSV即可。

凭借100k记录,ruby需要0.018秒/记录。在我看来,你的大部分时间将在Order.first_or_initialize内使用。这部分代码需要额外的往返数据库。初始化ActiveRecord也需要时间。但是要确定我会建议您对代码进行基准测试。

Benchmark.bm do |x|
   x.report("CSV evel") { CSV.foreach("orders.csv") {} }
   x.report("Init: ") { 1.upto(100_000) {Order.first_or_initialize(sku:  rand(...), customer_email: rand(...))} } # use rand query to prevent query caching 
   x.report('parse_currency') { 1.upto(100_000) { parse_currency(...} }
   x.report('parse_date') { 1.upto(100_000) { parse_date(...} }
end

您还应该在导入期间观察内存消耗。也许垃圾收集不能经常运行或者没有清理对象。

要获得速度,您可以按照 Matt Brictson 提示并绕过ActiveRecord。 您可以尝试使用gem activerecord-import,也可以开始并行,例如使用fork进行多处理或使用Thread.new进行多线程处理。