У меня есть задача импортировать около 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
Чтобы эффективно обрабатывать память, вы можете запускать 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
Я предполагаю, что эта строка orders = legacy_database.query("SELECT * FROM oc_order")
загружает всю таблицу в память, что очень неэффективно.
Вам нужно перебирать таблицы по партиям. В ActiveRecord для этого существует find_each. Возможно, вам захочется реализовать свой собственный пакетный запрос с помощью limit
и offset
, так как вы не используете ActiveRecord.
Отключение кэширование строк, в то время как итерация по коллекции заказов должна уменьшить потребление памяти:
orders.each(cache_rows: false) do |order|
есть драгоценный камень, который помогает нам сделать это под названием 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.
(cache_rows: false)
едва использует какую-либо память, сладкий