Я не могу вам сказать, источник утечки памяти, но я подсмотреть некоторые низко висящие плоды.
Но первые две вещи:
уверен, что ActiveRecord является правильным способом для копирования данных из одной базы данных в другую Вы? Я очень уверен, что это не так. У каждого крупного продукта базы данных есть надежные возможности экспорта и импорта, а производительность, которую вы увидите там, будет много, во много раз лучше, чем в Ruby, и вы всегда можете вызывать эти инструменты из своего приложения. Подумайте об этом, прежде чем продолжать этот путь.
Откуда взялось номер 10 000? Ваш код подсказывает, что вы знаете, что неплохо получить все записей сразу, но 10 000 все еще много записей. Вы можете увидеть некоторые выгоды, просто попробовав разные цифры: 100 или 1000, скажем.
Тем не менее, давайте копаться в то, что эта линия делает:
users = User.limit(10000).offset(offset).as_json
Первая часть, User.limit(10000).offset(offset)
создает объект ActiveRecord :: связь, представляющий запрос. Когда вы вызываете на него as_json
, выполняется запрос, который создает 100 000 объектов модели пользователя и помещает их в массив, а затем хэш создается из каждого из этих атрибутов объектов пользователя. (Посмотрите на источник для ActiveRecord::Relation#as_json
here.)
Другими словами, вы создаете объекты из 10 000 пользователей только для того, чтобы выбросить их после того, как у вас есть их атрибуты.
Итак, быстрый выигрыш - полностью пропустить эту часть. Просто выберите исходные данные:
user_keys = User.attribute_names
until break_condition
# ...
users_values = User.limit(10000).offset(offset).pluck(user_keys)
users_values.each do |vals|
user_attrs = user_keys.zip(vals).to_h
member = Member.find_by(name: user_attrs["name"])
member.update_attributes(user_attrs)
end
end
ActiveRecord::Calculations#pluck
возвращает массив массивов со значениями из каждой записи.Внутри цикла user_values.each
мы превращаем этот массив значений в хэш. Не нужно создавать экземпляры объектов пользователя.
Теперь давайте взглянем на это:
member = Member.find_by(name: user_attrs["name"])
member.update_attributes(user_attrs)
Это выбирает запись из базы данных, создает объект-члены, а затем обновляет запись в базе данных-10000 раз в в каждой итерации цикла while
, Это правильный подход , если вам нужны проверки для запуска при обновлении этой записи. Если вам не нужны валидации бежать, хотя, вы можете сэкономить время и память на, опять же, не инстанцирование любых объектов:
Member.where(name: user_attrs["name"]).update_all(user_attrs)
Разница заключается в том, что ActiveRecord::Relation#update_all
не выбирает запись из базы данных или экземпляра объект Member, он просто обновляет его. Вы сказали в своем комментарии выше, что у вас есть уникальное ограничение на столбец name
, поэтому мы знаем, что это обновит только одну запись.
Сделав эти изменения, вы должны по-прежнему бороться с тем фактом, что вам нужно выполнить 10 000 запросов UPDATE на каждой итерации цикла while
. Опять же, рассмотрите возможность использования встроенных функций экспорта и импорта баз данных вместо попыток сделать Rails этим.
'когда'? Вы имеете в виду 'while'? – matt
'более или менее выглядят так, может быть, лучше показать точный код? – fl00r
@ fl00r Точный код ожидает, что изменится класс или модель – Viren