完了するまでに通常数秒かかるプロセスがあるため、delayed_jobを使用して非同期で処理しようとしています。ジョブ自体は正常に機能します。私の質問は、ジョブをポーリングして、完了したかどうかを確認する方法です。
変数に割り当てるだけでdelayed_jobからIDを取得できます。
job = Available.delay.dosomething(:var => 1234)
+------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+
| id | priority | attempts | handler | last_error | run_at | locked_at | failed_at | locked_by | created_at | updated_at |
+------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+
| 4037 | 0 | 0 | --- !ru... | | 2011-04-... | | | | 2011-04... | 2011-04-... |
+------+----------+----------+------------+------------+-------------+-----------+-----------+-----------+------------+-------------+
ただし、ジョブが完了するとすぐに削除され、完了したレコードを検索するとエラーが返されます。
@job=Delayed::Job.find(4037)
ActiveRecord::RecordNotFound: Couldn't find Delayed::Backend::ActiveRecord::Job with ID=4037
@job= Delayed::Job.exists?(params[:id])
わざわざこれを変更し、完全なレコードの削除を延期する必要がありますか?他にどのようにしてステータスの通知を受け取ることができるかわかりません。それとも、完了の証拠としてデッドレコードをポーリングしても大丈夫ですか?他の誰かが似たようなことに直面していますか?
最終的に、Delayed_Jobとafter(job)コールバックを組み合わせて使用し、memcachedオブジェクトに作成されたジョブと同じIDを設定しました。このようにして、memcachedオブジェクトをポーリングする代わりに、データベースにアクセスしてジョブのステータスを要求する回数を最小限に抑えます。また、完了したジョブから必要なオブジェクト全体が含まれているため、ラウンドトリップリクエストもありません。ほぼ同じことをしたgithubの人たちの記事からアイデアを得ました。
https://github.com/blog/467-smart-js-polling
ポーリングにjqueryプラグインを使用しました。これは、ポーリングの頻度が少なく、一定の回数の再試行後にあきらめます。
https://github.com/jeremyw/jquery-smart-poll
うまく機能しているようです。
def after(job)
prices = Room.prices.where("space_id = ? AND bookdate BETWEEN ? AND ?", space_id.to_i, date_from, date_to).to_a
Rails.cache.fetch(job.id) do
bed = Bed.new(:space_id => space_id, :date_from => date_from, :date_to => date_to, :prices => prices)
end
end
APIから始めましょう。次のようなものが欲しいのですが。
@available.working? # => true or false, so we know it's running
@available.finished? # => true or false, so we know it's finished (already ran)
それでは、仕事を書いてみましょう。
class AwesomeJob < Struct.new(:options)
def perform
do_something_with(options[:var])
end
end
ここまでは順調ですね。私たちには仕事があります。それをエンキューするロジックを書いてみましょう。 Availableはこのジョブを担当するモデルなので、このジョブの開始方法を教えましょう。
class Available < ActiveRecord::Base
def start_working!
Delayed::Job.enqueue(AwesomeJob.new(options))
end
def working?
# not sure what to put here yet
end
def finished?
# not sure what to put here yet
end
end
では、仕事が機能しているかどうかをどうやって知るのでしょうか?いくつかの方法がありますが、Railsでは、モデルが何かを作成するときに、通常はその何かに関連付けられていると感じます。どのように関連付けるのですか?データベースでIDを使用します。job_id
を追加しましょう。利用可能なモデル。
その間、ジョブがすでに終了しているため、またはまだ開始されていないために、ジョブが機能していないことをどのようにして知ることができますか? 1つの方法は、ジョブが実際に何をしたかを実際に確認することです。ファイルを作成した場合は、ファイルが存在するかどうかを確認してください。値を計算した場合は、結果が書き込まれていることを確認してください。ただし、一部のジョブは、その作業の明確な検証可能な結果がない可能性があるため、チェックが簡単ではありません。このような場合、モデルでフラグまたはタイムスタンプを使用できます。これが私たちの場合であると仮定して、job_finished_at
タイムスタンプを追加して、まだ実行されていないジョブとすでに終了しているジョブを区別しましょう。
class AddJobIdToAvailable < ActiveRecord::Migration
def self.up
add_column :available, :job_id, :integer
add_column :available, :job_finished_at, :datetime
end
def self.down
remove_column :available, :job_id
remove_column :available, :job_finished_at
end
end
了解しました。それでは、start_working!
メソッドを変更して、ジョブをキューに入れるとすぐに、実際にAvailable
をそのジョブに関連付けましょう。
def start_working!
job = Delayed::Job.enqueue(AwesomeJob.new(options))
update_attribute(:job_id, job.id)
end
すごい。この時点でbelongs_to :job
を書くこともできましたが、実際には必要ありません。
これで、working?
メソッドの記述方法がわかりました。とても簡単です。
def working?
job_id.present?
end
しかし、どのようにしてジョブを終了としてマークするのでしょうか。仕事が仕事そのものよりもうまく終わったことを誰も知りません。それでは、available_id
を(オプションの1つとして)ジョブに渡して、ジョブで使用しましょう。そのためには、IDを渡すためにstart_working!
メソッドを変更する必要があります。
def start_working!
job = Delayed::Job.enqueue(AwesomeJob.new(options.merge(:available_id => id))
update_attribute(:job_id, job.id)
end
そして、ジョブにロジックを追加して、完了時にjob_finished_at
タイムスタンプを更新する必要があります。
class AwesomeJob < Struct.new(:options)
def perform
available = Available.find(options[:available_id])
do_something_with(options[:var])
# Depending on whether you consider an error'ed job to be finished
# you may want to put this under an ensure. This way the job
# will be deemed finished even if it error'ed out.
available.update_attribute(:job_finished_at, Time.current)
end
end
このコードを配置すると、finished?
メソッドの記述方法がわかります。
def finished?
job_finished_at.present?
end
これで完了です。これで、@available.working?
と@available.finished?
に対して簡単にポーリングできます。また、@available.job_id
をチェックすることで、Available用に作成された正確なジョブを知ることができます。 belongs_to :job
と言うことで、簡単に実際の関連付けに変えることができます。
最善の方法は、delayed_jobで利用可能なコールバックを使用することだと思います。これらは、:success、:error、および:afterです。したがって、次のようにモデルにコードを配置できます。
class ToBeDelayed
def perform
# do something
end
def after(job)
# do something
end
end
Obj.delayed.methodの使用を主張する場合は、Delayed :: PerformableMethodにモンキーパッチを適用し、そこにafter
メソッドを追加する必要があるためです。私見では、バックエンド固有の値(ActiveRecordとMongoidなど)をポーリングするよりもはるかに優れています。
これを実現する最も簡単な方法は、ポーリングアクションを次のようなものに変更することです。
def poll
@job = Delayed::Job.find_by_id(params[:job_id])
if @job.nil?
# The job has completed and is no longer in the database.
else
if @job.last_error.nil?
# The job is still in the queue and has not been run.
else
# The job has encountered an error.
end
end
end
なぜこれが機能するのですか?いつ Delayed::Job
キューからジョブを実行し、データベースからジョブを削除します成功した場合。ジョブが失敗した場合、レコードはキューに残り、後で再度実行されます。last_error
属性は発生したエラーに設定されます。上記の2つの機能を使用して、can削除されたレコードをチェックし、それらが成功したかどうかを確認します。
上記の方法の利点は次のとおりです。
次のような操作を行うことで、この機能をモデルメソッドにカプセル化できます。
# Include this in your initializers somewhere
class Queue < Delayed::Job
def self.status(id)
self.find_by_id(id).nil? ? "success" : (job.last_error.nil? ? "queued" : "failure")
end
end
# Use this method in your poll method like so:
def poll
status = Queue.status(params[:id])
if status == "success"
# Success, notify the user!
elsif status == "failure"
# Failure, notify the user!
end
end
ジョブが完了したという通知を受け取ることが重要な場合は、Available.delay.dosomething
を呼び出したときにキューに入れられるデフォルトのジョブに依存するのではなく、カスタムジョブオブジェクトを記述してキューに入れることをお勧めしますthat。次のようなオブジェクトを作成します。
class DoSomethingAvailableJob
attr_accessor options
def initialize(options = {})
@options = options
end
def perform
Available.dosomething(@options)
# Do some sort of notification here
# ...
end
end
そしてそれを次のようにキューに入れます:
Delayed::Job.enqueue DoSomethingAvailableJob.new(:var => 1234)
アプリケーションのdelayed_jobsテーブルは、実行中のジョブとキューに入れられたジョブのステータスのみを提供することを目的としています。これは永続的なテーブルではなく、パフォーマンス上の理由から実際にはできるだけ小さくする必要があります。そのため、ジョブは完了後すぐに削除されます。
代わりに、ジョブが完了したことを示すフィールドをAvailable
モデルに追加する必要があります。通常、ジョブの処理にかかる時間に関心があるため、start_timeフィールドとend_timeフィールドを追加します。次に、私のdosomething
メソッドは次のようになります。
def self.dosomething(model_id)
model = Model.find(model_id)
begin
model.start!
# do some long work ...
rescue Exception => e
# ...
ensure
model.finish!
end
end
開始!そして終了!メソッドは、現在の時刻を記録してモデルを保存するだけです。それなら私はcompleted?
AJAXがポーリングして、ジョブが終了したかどうかを確認できるメソッド。
def completed?
return true if start_time and end_time
return false
end
これを行うには多くの方法がありますが、この方法は簡単で、私にとってはうまく機能します。