在 Ruby/Rails 中处理大型数据集导入
Working with large dataset imports in Ruby/Rails
我目前正在使用 Ruby/Rails 进行一个项目,将发票导入数据库,但试图最大限度地提高流程效率,但现在确实太慢了。
对于具有 100.000 行的导入批次,处理和保存数据库中的每条记录大约需要 2.5 3 小时。
//// Ruby代码
class DeleteImportStrategy
def pre_process(merchant_prefix, channel_import)
# channel needed to identify invoices so an import from another channel cannot collude if they had same merchant_prefix
Jzbackend::Invoice.where(merchant_prefix: merchant_prefix, channel: channel_import.channel).delete_all
# get rid of all previous import patches which becomes empty after delete_import_strategy
Jzbackend::Import.where.not(id: channel_import.id).where(channel: channel_import.channel).destroy_all
end
def process_row(row, channel_import)
debt_claim = Jzbackend::Invoice.new
debt_claim.import = channel_import
debt_claim.status = 'pending'
debt_claim.channel = channel_import.channel
debt_claim.merchant_prefix = row[0]
debt_claim.debt_claim_number = row[1]
debt_claim.amount = Monetize.parse(row[2])
debt_claim.print_date = row[3]
debt_claim.first_name = row.try(:[], 4)
debt_claim.last_name = row.try(:[], 5)
debt_claim.address = row.try(:[], 6)
debt_claim.postal_code = row.try(:[], 7)
debt_claim.city = row.try(:[], 8)
debt_claim.save
end
结束
////
因此,对于以 CSV 形式传入的每个导入批次,我摆脱了以前的批次,并开始导入新的批次,方法是读取每一行并将其插入到新的导入为发票记录中。但是,100.000 个条目需要 2.5-3 小时似乎有点矫枉过正。我怎样才能优化这个过程,因为我确定这种方式绝对没有效率。
已编辑:
所以我已经很久没有发布这个了,但要注意的是,我最终使用了 activerecord-import 库,从那时起它运行得很好。但是,请注意它的 :on_duplicate_key_update 功能仅在 PostgreSQL v9.5+ 中可用。
批量导入第一条规则:批量,批量,批量。
您正在分别保存每一行。这会产生巨大的开销。比如说,插入本身需要 1 毫秒,但到数据库的往返是 5 毫秒。总使用时间 - 6 毫秒。对于 6000 毫秒或 6 秒的 1000 条记录。
现在假设您使用批量插入,在同一语句中为多行发送数据。它看起来像这样:
INSERT INTO users (name, age)
VALUES ('Joe', 20), ('Moe', 22), ('Bob', 33'), ...
假设您在这个请求中发送了 1000 行的数据。请求本身需要 1000 毫秒(但实际上它也可能会快得多,解析查询、准备执行计划等的开销更少)。总耗时为 1000ms + 5ms。至少减少 6 倍! (在我的实际项目中,我观察到 100x-200x 的减少)。
我目前正在使用 Ruby/Rails 进行一个项目,将发票导入数据库,但试图最大限度地提高流程效率,但现在确实太慢了。
对于具有 100.000 行的导入批次,处理和保存数据库中的每条记录大约需要 2.5 3 小时。
//// Ruby代码
class DeleteImportStrategy
def pre_process(merchant_prefix, channel_import)
# channel needed to identify invoices so an import from another channel cannot collude if they had same merchant_prefix
Jzbackend::Invoice.where(merchant_prefix: merchant_prefix, channel: channel_import.channel).delete_all
# get rid of all previous import patches which becomes empty after delete_import_strategy
Jzbackend::Import.where.not(id: channel_import.id).where(channel: channel_import.channel).destroy_all
end
def process_row(row, channel_import)
debt_claim = Jzbackend::Invoice.new
debt_claim.import = channel_import
debt_claim.status = 'pending'
debt_claim.channel = channel_import.channel
debt_claim.merchant_prefix = row[0]
debt_claim.debt_claim_number = row[1]
debt_claim.amount = Monetize.parse(row[2])
debt_claim.print_date = row[3]
debt_claim.first_name = row.try(:[], 4)
debt_claim.last_name = row.try(:[], 5)
debt_claim.address = row.try(:[], 6)
debt_claim.postal_code = row.try(:[], 7)
debt_claim.city = row.try(:[], 8)
debt_claim.save
end
结束
////
因此,对于以 CSV 形式传入的每个导入批次,我摆脱了以前的批次,并开始导入新的批次,方法是读取每一行并将其插入到新的导入为发票记录中。但是,100.000 个条目需要 2.5-3 小时似乎有点矫枉过正。我怎样才能优化这个过程,因为我确定这种方式绝对没有效率。
已编辑: 所以我已经很久没有发布这个了,但要注意的是,我最终使用了 activerecord-import 库,从那时起它运行得很好。但是,请注意它的 :on_duplicate_key_update 功能仅在 PostgreSQL v9.5+ 中可用。
批量导入第一条规则:批量,批量,批量。
您正在分别保存每一行。这会产生巨大的开销。比如说,插入本身需要 1 毫秒,但到数据库的往返是 5 毫秒。总使用时间 - 6 毫秒。对于 6000 毫秒或 6 秒的 1000 条记录。
现在假设您使用批量插入,在同一语句中为多行发送数据。它看起来像这样:
INSERT INTO users (name, age)
VALUES ('Joe', 20), ('Moe', 22), ('Bob', 33'), ...
假设您在这个请求中发送了 1000 行的数据。请求本身需要 1000 毫秒(但实际上它也可能会快得多,解析查询、准备执行计划等的开销更少)。总耗时为 1000ms + 5ms。至少减少 6 倍! (在我的实际项目中,我观察到 100x-200x 的减少)。