Я планирую использовать отложенное задание для запуска некоторой фоновой аналитики. В моем первоначальном тесте я видел огромное количество использования памяти, поэтому я в основном создал очень простую задачу, которая выполняется каждые 2 минуты, просто чтобы посмотреть, сколько памяти используется.
Задача очень проста и analytics_eligbile? метод всегда возвращает false, учитывая, где данные находятся сейчас, поэтому в основном не вызывается ни один из сложных кодов. У меня есть около 200 сообщений в моей выборке данных в разработке. Пост has_one analytics_facet.
Независимо от внутренней логики / бизнеса здесь, единственное, что делает эта задача, это вызывает analytics_elptable? метод 200 раз каждые 2 минуты. В течение 4 часов мое использование физической памяти составляет 110 МБ, а виртуальной памяти - 200 МБ. Просто для того, чтобы сделать что-то такое простое! Я даже не могу себе представить, сколько памяти это будет съесть, если он будет выполнять реальную аналитику на 10000 сообщений с реальными производственными данными !! Конечно, он может не работать каждые 2 минуты, больше как каждые 30, но я не думаю, что он будет летать.
Это работает ruby 1.9.7, rails 2.3.5 на Ubuntu 10.x 64 бит. Мой ноутбук имеет 4 ГБ памяти, двухъядерный процессор.
Действительно ли рельсы такие плохие или я что-то не так делаю?
Delayed::Worker.logger.info('RAM USAGE Job Start: ' + `pmap #{Process.pid} | tail -1`[10,40].strip)
Post.not_expired.each do |p|
if p.analytics_eligible?
#this method is never called
Post.find_for_analytics_update(p.id).update_analytics
end
end
Delayed::Worker.logger.info('RAM USAGE Job End: ' + `pmap #{Process.pid} | tail -1`[10,40].strip)
Delayed::Job.enqueue PeriodicAnalyticsJob.new(), 0, 2.minutes.from_now
def analytics_eligible?
vf = self.analytics_facet
if self.total_ratings > 0 && vf.nil?
return true
elsif !vf.nil? && vf.last_update_tv > 0
ratio = self.total_ratings / vf.last_update_tv
if (ratio - 1) >= Constants::FACET_UPDATE_ELIGIBILITY_DELTA
return true
end
end
return false
end
ActiveRecord довольно требователен к памяти — будьте очень осторожны при выборе и помните, что Ruby автоматически возвращает последний оператор в блоке в качестве возвращаемого значения, что может означать, что вы возвращаете массив записей, которые сохраняются. в результате где-то и, следовательно, не имеют права на сбор мусора.
Кроме того, когда вы вызываете "Post.not_expired.each", вы загружаете все ваши сообщения с сроком действия not_expired в оперативную память. Лучшим решением является find_in_batches, которое загружает только X записей в ОЗУ за раз.
Исправить это можно так просто:
def do_analytics
Post.not_expired.find_in_batches(:batch_size => 100) do |batch|
batch.each do |post|
if post.analytics_eligible?
#this method is never called
Post.find_for_analytics_update(post.id).update_analytics
end
end
end
GC.start
end
do_analytics
Здесь происходит несколько вещей. Во-первых, все это ограничено функцией, чтобы предотвратить коллизии переменных со ссылками из итераторов блоков. Затем find_in_batches извлекает объекты batch_size
из БД за раз, и до тех пор, пока вы не создаете ссылки на них, после каждой итерации вы получаете право на сборку мусора, что снижает общее использование памяти. Наконец, мы вызываем GC.start
в конце метода; это заставляет GC запускать развертку (чего вы не хотели бы делать в приложении реального времени, но, поскольку это фоновое задание, это нормально, если для его выполнения требуется дополнительные 300 мс). Он также имеет очень явное преимущество, если возвращает nil
, что означает, что результатом метода является nil
, а это означает, что мы не можем случайно удержать экземпляры AR, возвращенные из средства поиска. .
Использование чего-то подобного должно гарантировать, что вы не столкнетесь с утечкой объектов AR, и должно значительно улучшить как производительность, так и использование памяти. Вам нужно убедиться, что вы не допускаете утечки в другом месте вашего приложения (переменные класса, глобальные переменные и ссылки на классы являются худшими нарушителями), но я подозреваю, что это решит вашу проблему.
При всем при этом, на мой взгляд, это проблема cron (периодическая повторяющаяся работа), а не проблема DJ. У вас может быть одноразовый анализатор аналитики, который запускает вашу аналитику каждые X минут с помощью script/runner
, вызываемого cron, который очень аккуратно очищает любые потенциальные утечки памяти или неправильное использование за прогон (поскольку весь процесс заканчивается в конце)
Это факт, что Ruby потребляет (и утекает) память. Не знаю, сможете ли вы что-то с этим поделать, но по крайней мере я рекомендую вам взглянуть на Ruby Enterprise Edition .
REE - это порт с открытым исходным кодом, который, помимо прочего, обещает «на 33% меньше памяти». Я использую REE с Passenger в производстве уже почти два года, и я очень доволен.
Если вы испытываете проблемы с памятью, одним из решений является использование другой технологии фоновой обработки, например resque. Это обработка BG, используемая github.
Благодаря родительской и дочерней архитектуре Resque архитектуре, задания, которые используют слишком много памяти, освобождают эту память по завершения. Никакого нежелательного роста
Как?
На некоторых платформах, когда рабочий Resque worker резервирует задание, он немедленно создает дочерний процесс. Дочерний процесс обрабатывает задание, а затем завершается. Когда дочерний процесс успешно завершился, рабочий рабочий резервирует другое задание и повторяет процесс.
Более подробную техническую информацию вы можете найти в README.
Пакетная загрузка данных и агрессивное использование сборщика мусора, как предложил Крис Хилд, дадут вам действительно большие выгоды, но люди часто упускают из виду еще одну область — это то, в какие фреймворки они загружаются.
Загрузка стека Rails по умолчанию даст вам ActionController, ActionMailer, ActiveRecord и ActiveResource вместе. Если вы создаете веб-приложение, возможно, вы используете не все из них, но, вероятно, используете большинство из них.
Когда вы создаете фоновое задание, вы можете избежать загрузки вещей, которые вам не нужны, создав для этого пользовательскую среду:
# config/environments/production_bg.rb
config.frameworks -= [ :action_controller, :active_resource, :action_mailer ]
# (Also include config directives from production.rb that apply)
Каждая из этих платформ будет просто ждать письма, которое никогда не будет отправлено. отправлено, или контроллер, который никогда не будет вызван. Их просто нет смысла загружать. Отредактируйте файл database.yml
, настройте фоновое задание для запуска в среде production_bg
, и у вас будет гораздо более чистый лист для начала.
Еще одна вещь, которую вы можете сделать, это использовать ActiveRecord напрямую, вообще не загружая Rails. Это может быть все, что вам нужно для этой конкретной операции. Я также обнаружил, что использование облегченного ORM, такого как Sequel, делает вашу фоновую работу очень легкой, если вы в основном выполняете вызовы SQL для реорганизации записей или удаления старых данных. Если вам нужен доступ к вашим моделям и их методам, вам нужно будет использовать ActiveRecord. Однако иногда стоит повторно реализовать простую логику на чистом SQL из соображений производительности и эффективности.
При измерении использования памяти единственным числом, которое следует учитывать, является «реальная» память. Виртуальная сумма содержит общие библиотеки, и их стоимость распределяется между каждым процессом, использующим их, даже если она считается полностью для каждого из них.
В конце концов, если для запуска чего-то важного требуется 100 МБ памяти, но вы можете сократить его до 10 МБ за три недели работы, я не понимаю, зачем вам беспокоиться. 90 МБ памяти стоят максимум около 60 долларов в год у управляемого поставщика, что обычно намного дешевле, чем ваше время.
Ruby on Rails придерживается философии большей заботы о вашей продуктивности и времени, чем об использовании памяти. Если вы хотите подстричь его, посадить на диету, вы можете это сделать, но для этого потребуется немного усилий.