在ruby中运行rake导入任务时内存不足

我正在运行一项任务来导入大约100万个订单。 我循环遍历数据以将其更新为新数据库上的值,并且它在我的本地计算机上正常工作,具有8 gig的内存。

然而,当我将它上传到我的AWS实例t2.medium它将运行前50万行但是到最后,当它开始实际创建不存在的订单时,我将开始最大化我的记忆。 我正在将一个mysql数据库移植到postgres

我错过了一些明显的东西吗?

 require 'mysql2' # or require 'pg' require 'active_record' def legacy_database @client ||= Mysql2::Client.new(Rails.configuration.database_configuration['legacy_production']) end desc "import legacy orders" task orders: :environment do orders = legacy_database.query("SELECT * FROM oc_order") # init progressbar progressbar = ProgressBar.create(:total => orders.count, :format => "%E, \e[0;34m%t: |%B|\e[0m") orders.each do |order| if [1, 2, 13, 14].include? order['order_status_id'] payment_method = "wx" if order['paid_by'] == "Alipay" payment_method = "ap" elsif order['paid_by'] == "UnionPay" payment_method = "up" end user_id = User.where(import_id: order['customer_id']).first if user_id user_id = user_id.id end order = Order.create( # id: order['order_id'], import_id: order['order_id'], # user_id: order['customer_id'], user_id: user_id, receiver_name: order['payment_firstname'], receiver_address: order['payment_address_1'], created_at: order['date_added'], updated_at: order['date_modified'], paid_by: payment_method, order_num: order['order_id'] ) #increment progress bar on each save progressbar.increment end end end 

为了有效地处理内存,您可以按照nattfodd的建议批量运行mysql查询。

根据mysql文档 ,有两种方法可以实现它:

SELECT * FROM oc_order LIMIT 5,10;SELECT * FROM oc_order LIMIT 10 OFFSET 5;

两个查询都将返回第6-15行。

您可以决定所选的偏移量并循环运行查询,直到您的订单对象为空。

假设您一次处理1000个订单,那么您将拥有以下内容:

 batch_size = 1000 offset = 0 loop do orders = legacy_database.query("SELECT * FROM oc_order LIMIT #{batch_size} OFFSET #{offset}") break unless orders.present? offset += batch_size orders.each do |order| ... # your logic of creating new model objects end end 

还建议在生产中运行您的代码并进行适当的error handling:

 begin ... # main logic rescue => e ... # handle error ensure ... # ensure end 

我假设这行orders = legacy_database.query("SELECT * FROM oc_order")将整个表加载到内存中,这是非常无效的。

您需要批量迭代表。 在ActiveRecord中,有find_each方法。 您可能希望使用limitoffset实现自己的批量查询,因为您不使用ActiveRecord。

迭代订单集合时禁用行缓存应减少内存消耗:

 orders.each(cache_rows: false) do |order| 

有一个gem帮助我们做这个叫做activerecord-import 。

 bulk_orders=[] orders.each do |order| order = Order.new( # id: order['order_id'], import_id: order['order_id'], # user_id: order['customer_id'], user_id: user_id, receiver_name: order['payment_firstname'], receiver_address: order['payment_address_1'], created_at: order['date_added'], updated_at: order['date_modified'], paid_by: payment_method, order_num: order['order_id'] ) end Order.import bulk_orders, validate: false 

使用单个INSERT语句。