Rails Web要求から本当に長いタスクを実行する方法
最近、我々の管理は、大量に請求書を輸出する方法を必要としました.マネージャがWebフォームでバッチの最初と最後の請求書を選択した後、非同期プロセスは、請求書のPDFファイルを生成し、ZIPファイルにそれらをパックし、エクスポートをダウンロードするためのリンクをマネージャーにメールを送信開始する必要があります.現在、PDFを生成するのは遅いです.請求書を数百または数千を含む大きなバッチについては、このプロセスは簡単に10または15分またはそれ以上を取ることができます.
では、どのようにしてRailsリクエストからそのような長期的なプロセスを引き起こすのでしょうか?気になる最初のオプションは、キューバックエンドのいくつかによって実行されるバックグラウンドジョブですSidekiq , Resque or DelayedJob , 多分支配されるActiveJob . これが確かに働く間、これらの解決策の問題は通常、彼らがサーバーで利用できる限られた数の労働者を持っているということです、そして、我々はそれほど長い間他の重要なバックグラウンドタスクを潜在的にブロックしたくありませんでした.
代わりに我々はRails要求から新しい、別のプロセスを実行することでした.走るようなものRake task しかし、ウェブ要求によって引き起こされます.実際には、バルクのエクスポートはすでにrakeタスクとして実装されていたので、我々は実際に私たちの管理のWebインターフェイスからこのタスクをアクセスできるようにすることでした.
Unixライクなシステムで新しいプロセスを生成する標準的な方法は
The 子プロセスは、それから 最終的な子プロセスは、親プロセスからすべての重要な設定を継承します.これが我々が単に走ることができる理由です ブロック内のコードはシェルのリダイレクトを使用しているので、子プロセスは直接実行されませんが、通常は デフォルトでは、オペレーティングシステムは親プロセスが子プロセス終了ステータスに興味を持っていることを予想します.我々はそうではありません-我々はRakeタスクを実行して、それについて忘れてください、タスクはそれ自身で最終的な電子メールを送ることのような他のすべてを取り扱います.そういうわけで我々は電話します
もし私たちのコードをより移植性(Windows上で使用可能)にしたいなら、私たちは
Rakeタスクをスケジューリングするほぼ同等の方法
コントローラからのそのような長期的なプロセスを引き起こすことは安全ではないことを心に留めておいてください.前の例では、
テクニックはおそらく、彼らが何をしているかを知っている限られた数の人々にアクセス可能な内部管理領域のような非常に制御された環境だけでOKです、そして、機能が控え目に使用されるとき.このRAKEタスクを公開したい場合(例えば「データ取出し」機能のように)、上記のような実際の待ち行列システム、あるいはシステムレベルでの待ち行列デーモンに確実に頼ります.
とにかく、我々のユースケースのために、コントローラからRAKEタスクを直接フォークすることは最も実用的な方法でした.
あなたがこのように将来のポストを逃したくないならば、ここで、または、私に続いてください.乾杯!
では、どのようにしてRailsリクエストからそのような長期的なプロセスを引き起こすのでしょうか?気になる最初のオプションは、キューバックエンドのいくつかによって実行されるバックグラウンドジョブですSidekiq , Resque or DelayedJob , 多分支配されるActiveJob . これが確かに働く間、これらの解決策の問題は通常、彼らがサーバーで利用できる限られた数の労働者を持っているということです、そして、我々はそれほど長い間他の重要なバックグラウンドタスクを潜在的にブロックしたくありませんでした.
代わりに我々はRails要求から新しい、別のプロセスを実行することでした.走るようなものRake task しかし、ウェブ要求によって引き起こされます.実際には、バルクのエクスポートはすでにrakeタスクとして実装されていたので、我々は実際に私たちの管理のWebインターフェイスからこのタスクをアクセスできるようにすることでした.
プロセスをフォークする
Unixライクなシステムで新しいプロセスを生成する標準的な方法は
fork
それ.Railsコントローラではfork
Rakeタスクは次のようになります.class BulkInvoiceExportsController < ApplicationController
def create
child = fork do
exec("bin/rails export_invoices FROM=20220001 TO=20220100 \\
>> /tmp/bulk_invoices_export.log 2>&1")
end
Process.detach(child)
end
end
コードについていくつかのことに注意しましょうthis StackOverflow answer :Process#fork
メソッドは現在のプロセス(現在のスレッド)を2つのコピーに分割し、新しい子プロセスがブロック内のコードを実行します.Process#exec
. bin/rails
最初に正しいRubyをセットアップする必要がない(例えばRubyバージョンマネージャを使用しても)rvm
, rbenv
or chruby
) を指定しない./bin/sh
). リダイレクションは、我々がRakeタスクで何が起こっているかをデバッグして、モニターするのを許します.Process#detach
私たちは子供のプロセスを気にしないと蓄積を防ぐために知っているOSを聞かせてzombie processes . プロセスの「産卵」
もし私たちのコードをより移植性(Windows上で使用可能)にしたいなら、私たちは
Process#spawn
の代わりにfork
, ASsuggested Rubyドキュメントで.The spawn
また、子プロセス環境、ファイル記述子、制限、または作業ディレクトリを微調整できます.Rakeタスクをスケジューリングするほぼ同等の方法
spawn
このように書くことができます.class BulkInvoiceExportsController < ApplicationController
def create
child = spawn("bin/rails export_invoices FROM=20220001 TO=20220100",
%i[out err] => %w[/tmp/bulk_invoices_export.log a])
Process.detach(child)
end
end
セキュリティ警告
コントローラからのそのような長期的なプロセスを引き起こすことは安全ではないことを心に留めておいてください.前の例では、
create
コントローラの動作は、CPUとメモリリソースのおそらくかなりの部分を消費し、おそらくあなたのデータベースサーバーへのより多くの接続を開く、1つの外部レールのプロセスを産卵につながる.これは非常に脆弱なセットアップですDoS attacks . テクニックはおそらく、彼らが何をしているかを知っている限られた数の人々にアクセス可能な内部管理領域のような非常に制御された環境だけでOKです、そして、機能が控え目に使用されるとき.このRAKEタスクを公開したい場合(例えば「データ取出し」機能のように)、上記のような実際の待ち行列システム、あるいはシステムレベルでの待ち行列デーモンに確実に頼ります.
atd
これは、サーバーの負荷に基づいてタスクを保持することができます).とにかく、我々のユースケースのために、コントローラからRAKEタスクを直接フォークすることは最も実用的な方法でした.
あなたがこのように将来のポストを逃したくないならば、ここで、または、私に続いてください.乾杯!
Reference
この問題について(Rails Web要求から本当に長いタスクを実行する方法), 我々は、より多くの情報をここで見つけました https://dev.to/nejremeslnici/how-to-run-a-really-long-task-from-a-rails-web-request-47fbテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol