Недостаточно памяти при запуске задачи импорта rake в ruby

0

У меня есть задача импортировать около 1 миллиона заказов. Я перебираю данные, чтобы обновить их до значений в новой базе данных, и он отлично работает на моем локальном компьютере с 8 gig для ram.

Однако, когда я загружаю его в свой экземпляр AWS t2.medium Он будет работать для первых 500 тысяч строк, но ближе к концу, я начну максимизировать свою память, когда она начнет фактически создавать несуществующие заказы. Я переношу базу данных 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

4 ответа

2
Лучший ответ

Чтобы эффективно обрабатывать память, вы можете запускать mysql-запрос пакетами, как предложено nattfodd.

Есть два способа достичь этого, согласно документация 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

Также рекомендуется запускать ваш код при правильной обработке ошибок:

begin
  ... # main logic
rescue => e
  ... # handle error
ensure
  ... # ensure 
end
  • 1
    Это в сочетании с (cache_rows: false) едва использует какую-либо память, сладкий
3

Я предполагаю, что эта строка orders = legacy_database.query("SELECT * FROM oc_order") загружает всю таблицу в память, что очень неэффективно.

Вам нужно перебирать таблицы по партиям. В ActiveRecord для этого существует find_each. Возможно, вам захочется реализовать свой собственный пакетный запрос с помощью limit и offset, так как вы не используете ActiveRecord.

1

Отключение кэширование строк, в то время как итерация по коллекции заказов должна уменьшить потребление памяти:

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

есть драгоценный камень, который помогает нам сделать это под названием 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.

  • 0
    один оператор вставки с 1 миллионом записей также вызовет ошибку максимального пакета в MySQL.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню