クエリセット内の最初のインスタンスのアトミック更新を行います
質問
私は労働者-マシンの数にジョブを提供する際にレースの条件の数を処理しなければならないシステムに取り組んでいます。
クライアントステータスとジョブのためにシステムを問い合わせることになる=「0」(ToDoリスト)、次いで、アトミックな方法で、ステータスが「最も古い」行を更新=「1」(ロック)とその行のIDを取得します(マシンはなど、それに取り組んでいるように、作業者情報と仕事を更新する。)。
ここでの主な問題は、同時に更新する任意の数のクライアントがあるかもしれないということです。ソリューションは、ステータス=「0」、最も古いものを更新し、その後、再びすべてのロックを解除して約20行のをロックすることです。私はTransactionMiddlewareに見てきたが、私はそれを照会した後、私は私の下から更新されて、これは最も古い1のケースを妨げる方法を確認しません。
私はQuerySet.update()のものに見てきた、そしてそれは有望に見えるが、同じレコードのホールドを取得する2つのクライアントの場合には、ステータスが単に更新されます、そして我々は2人の労働者が働いています同じジョブ..私はここで途方に暮れて本当に思います。
私も見つけチケットきれいにケースを処理するようだが、私が持っている#2705 の(最後の更新が簡単に差分ですが、私は、コードのトランクであることをマージする方法がわからない)ので、私の限られたSVNの経験、そこからコードを取得する方法がないという考えます。
コード:結果=ジョブ
class Result(models.Model):
"""
Result: completed- and pending runs
'ToDo': job hasn't been acquired by a client
'Locked': job has been acquired
'Paused'
"""
# relations
run = models.ForeignKey(Run)
input = models.ForeignKey(Input)
PROOF_CHOICES = (
(1, 'Maybe'),
(2, 'No'),
(3, 'Yes'),
(4, 'Killed'),
(5, 'Error'),
(6, 'NA'),
)
proof_status = models.IntegerField(
choices=PROOF_CHOICES,
default=6,
editable=False)
STATUS_CHOICES = (
(0, 'ToDo'),
(1, 'Locked'),
(2, 'Done'),
)
result_status = models.IntegerField(choices=STATUS_CHOICES, editable=False, default=0)
# != 'None' => status = 'Done'
proof_data = models.FileField(upload_to='results/',
null=True, blank=True)
# part of the proof_data
stderr = models.TextField(editable=False,
null=True, blank=True)
realtime = models.TimeField(editable=False,
null=True, blank=True)
usertime = models.TimeField(editable=False,
null=True, blank=True)
systemtime = models.TimeField(editable=False,
null=True, blank=True)
# updated when client sets status to locked
start_time = models.DateTimeField(editable=False)
worker = models.ForeignKey('Worker', related_name='solved',
null=True, blank=True)
解決
あなたのジャンゴに#2705をマージするには、まずそれをダウンロードする必要があります:
cd <django-dir>
wget http://code.djangoproject.com/attachment/ticket/2705/for_update_11366_cdestigter.diff?format=raw
その後、必要に応じてDjangoのバージョンに戻しSVNます:
svn update -r11366
それを適用します:
patch -p1 for_update_11366_cdestigter.diff
これは、ファイルが正常にパッチが適用されたとされなかったが通知されます。競合の可能性は低い場合は、手動でhttp://code.djangoproject.com/attachment/ticket/2705/for_update_11366_cdestigter.diff
を見て、それらを修正することができます。
に適用を解除するパッチだけ書き込み
svn revert --recursive .
他のヒント
は、実装の詳細が明らかにされていないとして、擬似コードをすみません...それを行うにははるかに簡単な方法があります。
from threading import Lock
workers_lock = Lock()
def get_work(request):
workers_lock.acquire()
try:
# Imagine this method exists for brevity
work_item = WorkItem.get_oldest()
work_item.result_status = 1
work_item.save()
finally:
workers_lock.release()
return work_item
あなたは私の頭の上から2つの選択肢があります。一つは、検索するとすぐに行をロックし、適切なものが使用されているとしてマークされた後にのみロックを解除することです。ここでの問題は、他のクライアント・プロセスでも選択されません仕事を見ることができないことです。あなたはいつもちょうど自動的に最後の一つを選択している場合、それはo.k.なるようにウィンドウを簡単に十分かもしれあなたのために。
他のオプションは、クエリの時に戻って開いている行を持参するだろうが、クライアントはと仕事にジョブをつかむしようとするたび、再度チェックします。それに仕事にチェックを仕事を更新する場合は、クライアントの試みは、まず、それはまだ使用可能ですかどうかを確認するために行われることになります。他の誰かがすでにそれをつかんでいた場合、通知は、クライアントに送信されます。これは、すべてのクライアントがスナップショットとしての仕事のすべてを見ることができますが、彼らは常に最新のものをつかんでいるなら、あなたは、クライアントが常にジョブがすでに使用中であることの通知を受けている可能性があります。多分これはあなたが参照している先の競合状態がありますか?
それは、彼らが常に同じリストを取得されていませんように、クライアントに特定のグループ内のジョブを返すことであろう周りを取得するための一つの方法。たとえば、地域別あるいは単にランダムにそれらを打破します。例えば、各クライアントは9まで0のIDを持っている可能性が仕事上のIDのMODを取り、クライアントに同じエンディング数字でこれらのジョブを送り返します。あなたが到達できないという仕事があることを望んでいないように、しかしちょうどそれらの仕事にそれを制限しないでください。だから、例えば、あなたは1、2、および3のクライアントを持っていない場合や104の仕事は、誰もがそれを取得することができるだろう。だから、正しい終了桁のジョブとの十分な仕事がない一度は、単にリストを埋めるために他の数字で戻って来て開始します。あなたはここに正確なアルゴリズムで遊んでする必要があるかもしれませんが、うまくいけば、これはあなたのアイデアを提供します。
あなたがそれらを更新および/または通知を返送するために、データベース内の行をロックする方法は、主に、あなたのRDBMSに依存します。 MS SQL Serverでは、あなたが長いユーザーの介入は、それの途中で必要とされないほどのストアドプロシージャでうまくその仕事のすべてを包むことができます。
私はこのことができます願っています。