私はこのようなモデルを持っています
class Thingy(models.Model):
# ...
failures_count = models.IntegerField()
これを行う必要がある同時プロセス (Celery タスク) があります。
- なんらかの処理をする
failures_counter
処理がそれぞれのインクリメントに失敗した場合Thingy
failures_counter
一部のしきい値を超えた場合、Thingy
警告を発行しますが、警告は 1 つだけです。
明示的なロックを使用するなど、競合状態なしでこれを行う方法についていくつかのアイデアがあります(経由select_for_update
):
@transaction.commit_on_success
def report_failure(thingy_id):
current, = (Thingy.objects
.select_for_update()
.filter(id=thingy_id)
.values_list('failures_count'))[0]
if current == THRESHOLD:
issue_warning_for(thingy_id)
Thingy.objects.filter(id=thingy_id).update(
failures_count=F('failures_count') + 1
)
または、同期に Redis (既に存在します) を使用します。
@transaction.commit_on_success
def report_failure(thingy_id):
Thingy.objects.filter(id=thingy_id).update(
failures_count=F('failures_count') + 1
)
value = Thingy.objects.get(id=thingy_id).only('failures_count').failures_count
if value >= THRESHOLD:
if redis.incr('issued_warning_%s' % thingy_id) == 1:
issue_warning_for(thingy_id)
どちらのソリューションもロックを使用します。私はPostgreSQLを使用しているので、ロックせずにこれを達成する方法はありますか?
回答を含めるように質問を編集しています(Sean Vieira に感謝します。以下の回答を参照してください)。ロックを回避する方法について尋ねられた質問と、この回答は、PostgreSQL によって実装されているマルチバージョン同時実行制御(MVCC) を活用するという点で最適です。
この特定の質問では、PostgreSQL 機能の使用が明示的に許可されており、多くの RDBMS が を実装UPDATE ... RETURNING
していますが、標準 SQL ではなく、そのままでは Django の ORM でサポートされていないため、 を介して生の SQL を使用する必要がありますraw()
。同じ SQL ステートメントは他の RDBMS でも機能しますが、すべてのエンジンでは、同期、トランザクションの分離、および同時実行モデルに関する独自の議論が必要です (たとえば、MyISAM を使用する MySQL は引き続きロックを使用します)。
def report_failure(thingy_id):
with transaction.commit_on_success():
failure_count = Thingy.objects.raw("""
UPDATE Thingy
SET failure_count = failure_count + 1
WHERE id = %s
RETURNING failure_count;
""", [thingy_id])[0].failure_count
if failure_count == THRESHOLD:
issue_warning_for(thingy_id)