9

遅延ジョブを使用してバックグラウンド分析を実行することを計画しています。最初のテストでは、大量のメモリ使用量が確認されたため、基本的に、使用されているメモリ量を監視するためだけに2分ごとに実行される非常に単純なタスクを作成しました。

タスクは非常に単純で、analytics_eligbile?データが現在どこにあるかを考えると、メソッドは常にfalseを返すため、基本的に、ヒットするコードは呼び出されません。開発中のサンプルデータには約200件の投稿があります。has_oneanalytics_facetを投稿します。

ここでの内部ロジック/ビジネスに関係なく、このタスクが実行しているのはanalytics_eligibleを呼び出すことだけですか?2分ごとに200回メソッド。たった4時間で、私の物理メモリ使用量は110MB、仮想メモリは200MBになります。こんなに簡単なことをするためだけに!実際の本番データを使用して10,000件の投稿に対して実際の分析を行うと、これがどれだけのメモリを消費するか想像さえできません。確かに、30分ごとのように、2分ごとに実行されない可能性がありますが、それでも飛ぶとは思いません。

これは、Ubuntu 10.x64ビットでruby1.9.7、rails2.3.5を実行しています。私のラップトップには4GBのメモリ、デュアルコアCPUが搭載されています。

Railsは本当にこれほど悪いのでしょうか、それとも私は何か間違ったことをしているのでしょうか?

 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
4

4 に答える 4

20

ActiveRecordはかなりメモリを消費します-selectを実行するときは非常に注意してください。また、Rubyはブロック内の最後のステートメントを戻り値として自動的に返すことに注意してください。これは、結果として保存されるレコードの配列を返すことを意味する可能性があります。どこかにあるため、GCの対象にはなりません。

さらに、「Post.not_expired.each」を呼び出すと、すべてのnot_expired投稿がRAMにロードされます。より良い解決策はfind_in_batchesです。これは、具体的には一度にXレコードのみをRAMにロードします。

それを修正するのは、次のような簡単なことかもしれません。

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にDBからオブジェクトを取得し、それらへの参照を作成していない限り、各反復の実行後にガベージコレクションの対象となり、合計メモリ使用量を抑えます。GC.start最後に、メソッドの最後に呼び出します。これにより、GCはスイープを開始します(リアルタイムアプリでは実行したくない場合がありますが、これはバックグラウンドジョブであるため、実行に300ミリ秒余分にかかる場合でも問題ありません)。また、を返す場合にも非常に明確な利点があります。これnilは、メソッドの結果がでnilあるということです。つまり、ファインダーから返されたARインスタンスを誤って保持することはできません。

このようなものを使用すると、ARオブジェクトがリークすることがなくなり、パフォーマンスとメモリ使用量の両方が大幅に向上するはずです。アプリの他の場所にリークしていないことを確認する必要があります(クラス変数、グローバル、およびクラス参照は最悪の違反者です)が、これで問題が解決すると思います。

とはいえ、私の意見では、これはDJの問題ではなく、cronの問題(定期的な繰り返し作業)です。cronによって呼び出され、 X分ごとに分析を実行するワンショット分析パーサーを使用できますscript/runner。これにより、実行ごとに発生する可能性のあるメモリリークや誤用が非常にきれいにクリーンアップされます(プロセス全体が最後に終了するため)

于 2010-08-27T06:44:15.277 に答える
6

Chris Healdが提案したように、データをバッチでロードし、ガベージコレクターを積極的に使用すると、非常に大きなメリットが得られますが、人々が見落としがちな別の領域は、ロードしているフレームワークです。

デフォルトのRailsスタックをロードすると、ActionController、ActionMailer、ActiveRecord、ActiveResourceがすべて一緒に提供されます。Webアプリケーションを構築している場合、これらすべてを使用しているわけではないかもしれませんが、おそらくほとんどを使用しています。

バックグラウンドジョブを構築しているときは、そのためのカスタム環境を作成することで、不要なものの読み込みを回避できます。

# 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環境で実行するように設定すると、最初ははるかにクリーンな状態になります。

もう1つできることは、RailsをまったくロードせずにActiveRecordを直接使用することです。この特定の操作に必要なのはこれだけかもしれません。また、 Sequelのような軽量のORMを使用すると、レコードを再編成したり古いデータを削除したりするために主にSQL呼び出しを行う場合に、バックグラウンドジョブが非常に軽量になることもわかりました。ただし、モデルとそのメソッドにアクセスする必要がある場合は、ActiveRecordを使用する必要があります。ただし、パフォーマンスと効率の理由から、純粋なSQLで単純なロジックを再実装する価値がある場合もあります。

メモリ使用量を測定する場合、関係する唯一の数値は「実際の」メモリです。仮想量には共有ライブラリが含まれており、これらのコストは、各プロセスで完全にカウントされていても、それらを使用するすべてのプロセスに分散されます。

結局、何か重要なものを実行するには100MBのメモリが必要ですが、3週間の作業で10MBまで下げることができるのであれば、なぜわざわざするのかわかりません。マネージドプロバイダーでは、90MBのメモリコストは最大で年間約60ドルであり、通常は時間よりもはるかに安価です。

Ruby on Railsは、メモリ使用量よりも生産性と時間に関心を持つという哲学を取り入れています。あなたがそれを元に戻したいならば、それをダイエットに入れてください、あなたはそれをすることができます、しかしそれは少しの努力を要します。

于 2010-08-28T17:57:16.703 に答える
1

メモリの問題が発生している場合、1つの解決策は、resqueなどの別のバックグラウンド処理技術を使用することです。githubで使用されているBG処理です。

Resqueの親/子アーキテクチャのおかげで、メモリを使いすぎるジョブは、完了時にそのメモリを解放します。不要な成長はありません

どのように?

特定のプラットフォームでは、Resqueワーカーがジョブを予約すると、すぐに子プロセスをフォークします。子はジョブを処理してから終了します。子が正常に終了すると、ワーカーは別のジョブを予約し、プロセスを繰り返します。

技術的な詳細については、READMEをご覧ください。

于 2010-08-23T15:43:17.653 に答える
0

Rubyがメモリを消費(およびリーク)するのは事実です。それについて多くのことができるかどうかはわかりませんが、少なくともRubyEnterpriseEditionを確認することをお勧めします

REEはオープンソースポートであり、他のすべての優れた点の中で「33%少ないメモリ」を約束します。私はREEwithPassengerを2年近く生産に使用しており、非常に満足しています。

于 2010-08-18T13:08:08.343 に答える