次の環境の本番Webサイトがあります。
- レール2.3.5
- MySQLサーバー5.1.33
- Enterprise Ruby 1.8.6(2008-08-11パッチレベル287)[x86_64-linux]
- mysql gem 2.7
- バックグラウンドタスク用に4つの異なるサーバーで実行されている古いバージョンのBackgrounDRbプラグインで、それぞれ5つの異なるワーカーがあります(Rubyスレッド、個別のプロセスではありません!)。
BackgrounDRbワーカーの1つは、「楽観的ロック」のバリエーションを使用してジョブキューを処理します。
update_sql = "update jobs
set updated_at = CURRENT_TIMESTAMP,
in_process = 1
where id = #{job.id} and in_process = 0"
affected_rows = Job.connection.update(update_sql)
captured_job = affected_rows > 0 ? Job.find(job.id) : nil
上記のコードは、指定されたIDとin_processフィールドの追加条件でレコードを更新しようとします。したがって、この同じレコードが別のサーバー/プロセスによってすでに更新されている場合、UPDATEステートメントは0(ゼロ)を返すだけで、ジョブは2つの異なるサーバーによって同時に処理されません。
問題は次のとおりです。レコードが実際に更新された場合でも、「Job.connection.update(update_sql)」が0(ゼロ)を返すことがあります。大量のロギングがコードに追加された後でのみ、それを見つけることができました。それは私たちが重い負荷をかけている夜のプロダクションでのみ起こります...
私の推測では、mysql gemは、BackgrounDRbプロセスの5つのスレッドすべてで共有されるaffected_rowsにグローバル変数(クラス変数)を使用していると思いますが、よくわかりません。mysql gemとActiveRecordのコードを調べていましたが、実際にどのように機能するのか理解できませんでした。
これはどのように起こりますか?
更新2010-07-07:ジョブ処理にスレッドを使用しないことにしました-これですべての問題が解決します:すべてのジョブプロセッサは個別のプロセスになります:)