Rails 3.0.19
を使用して、アプリ ( ruby 1.9.2
) に取り組んでいMySQL 5.1
ます。実際のコードから少し抽象化すると、次のようなものになります。
Widgets
およびそれらParts
にはname
属性があり、 の名前Parts
は関連する の名前から派生する場合がありWidget
ます。当然、 の名前が更新されたときに、Widget
の名前も更新したいと思いParts
ます。これには妥当な時間 (~60 秒) がかかる可能性があるため、バックグラウンド ジョブで実行したいと考えています。したがって:
class Widget < ActiveRecord::Base
has_many :parts
after_save :update_part_names
def update_part_names
if name_was && name_changed?
Resque.enqueue Widget, { 'widget' => self.id, 'old_name' => name_was }
end
end
def self.perform(args)
widget = Widget.find(args['widget'])
widget.parts.each do |part|
new_name = part.name.sub(args['old_name'], widget.name)
part.name = new_name
part.save!
end
end
end
さて、私の開発環境では、これはうまく機能します。しかし、このコードをステージング環境にプッシュします。ステージング環境では、アプリ サーバーとは別のボックスで多くの resque ワーカーが実行されています。更新がキューに入れられ、正常に完了したように見えますが、実際の更新は一部のWidget.name
更新で行われ、他の更新では行われません。コンソールから実行するWidget.perform
と、100% の確率で動作します。
私の仮説は、これは競合状態であるというものでした。より多くのことが並行して発生するステージング環境では、ジョブがキューに入れられ、save
トランザクションWidget
が完了する前に実行されていました (これには 1 秒かかる場合があります。Widgets
多くの複雑なオブジェクトです)。協会)。したがって、Widget.find
resque ジョブではWidget
、まだ古い名前を持つレコードをロードしていたため、part.name.sub(args['old_name'], self.name)
何もしていませんでした。
ジョブのメソッドに次のコードを追加してみました:
def self.perform(args)
widget = Widget.find(args['widget'])
if widget.name == args['old_name']
Resque.enqueue Widget, args
else
# run as before
Widget
名前の更新がまだコミットされていない限り、これはジョブを再キューイングし続け、その後成功すると考えられていました。part
しかし、名前が時々更新されるという動作がまだ見られますが、常にではありません。(そして、私が知る限り、ジョブが更新ごとに複数回キューに入れられることはありません。)
2 つの質問: (1) そもそも私の問題の診断は間違っているのでしょうか? (2) 更新ジョブを毎回正常に実行するにはどうすればよいですか?
編集:これが本当に競合状態であることをますます確信しています。sleep 60
前にバックグラウンド ジョブに追加すると、 Widget.find
100% の確率で更新が正常に行われるようです。しかし、私はそれを受け入れられる解決策とは考えていません。