これは、潜在的な競合状態を伴うDjangoビューの簡単な例です。
# myapp/views.py
from Django.contrib.auth.models import User
from my_libs import calculate_points
def add_points(request):
user = request.user
user.points += calculate_points(user)
user.save()
競合状態はかなり明白である必要があります。ユーザーはこの要求を2回行うことができ、アプリケーションはuser = request.user
を同時に実行して、一方の要求が他方をオーバーライドする可能性があります。
関数calculate_points
が比較的複雑で、単一のupdate
に配置できず、ストアドプロシージャに配置するのが難しい、あらゆる種類の奇妙なものに基づいて計算を行うとします。
だからここに私の質問があります:これに似た状況に対処するために、Djangoはどのような種類のロックメカニズムを利用できますか?
Django 1.4+は select_for_update をサポートします。以前のバージョンでは、生のSQLクエリを実行できます。 select ... for update
基になるDBに応じて、更新から行をロックします。トランザクションが終了するまで、その行で必要なことを実行できます。例えば.
from Django.db import transaction
@transaction.commit_manually()
def add_points(request):
user = User.objects.select_for_update().get(id=request.user.id)
# you can go back at this point if something is not right
if user.points > 1000:
# too many points
return
user.points += calculate_points(user)
user.save()
transaction.commit()
Django 1.1以降、ORMのF()式を使用して、この特定の問題を解決できます。
from Django.db.models import F
user = request.user
user.points = F('points') + calculate_points(user)
user.save()
詳細については、ドキュメントを参照してください。
https://docs.djangoproject.com/en/1.8/ref/models/expressions/#Django.db.models.F
データベースロックはここに行く方法です。 Django( here )に "select for update"サポートを追加する計画がありますが、今のところ最も簡単なのは、生のSQLを使用してユーザーオブジェクトを更新することです。スコアの計算を開始します。
ペシミスティックロックは、基盤となるDB(Postgresなど)がサポートしている場合、Django 1.4のORMでサポートされるようになりました。 Django 1.4a1リリースノート を参照してください。
この種のものをシングルスレッド化する方法はたくさんあります。
1つの標準的なアプローチは最初に更新です。行の排他ロックを取得する更新を実行します。その後、あなたの仕事をします。そして最後に変更をコミットします。これを機能させるには、ORMのキャッシュをバイパスする必要があります。
もう1つの標準的なアプローチは、Webトランザクションを複雑な計算から分離する別個のシングルスレッドアプリケーションサーバーを用意することです。
Webアプリケーションは、スコアリング要求のキューを作成し、別のプロセスを生成してから、スコアリング要求をこのキューに書き込むことができます。スポーンはDjangoのurls.py
に配置できるため、Webアプリの起動時に発生します。または、別のmanage.py
管理スクリプトに入れることもできます。または、最初のスコアリング要求が試行されたときに「必要に応じて」実行することもできます。
Urllib2を介してWSリクエストを受け入れるWerkzeugを使用して、別のWSGIフレーバーのWebサーバーを作成することもできます。このサーバーのポート番号が1つしかない場合、要求はTCP/IPによってキューに入れられます。 WSGIハンドラーにスレッドが1つある場合は、シリアル化されたシングルスレッドを実現しています。スコアリングエンジンはWSリクエストであり、どこでも実行できるため、これは少しスケーラブルです。
さらに別のアプローチは、計算を行うために取得して保持する必要のある他のリソースを用意することです。
データベース内のシングルトンオブジェクト。一意のテーブルの単一の行をセッションIDで更新して、制御を取得できます。 None
のセッションIDで更新して、制御を解放します。重要な更新には、他の誰かがロックを保持しているときに更新が失敗することを保証するために、WHERE SESSION_ID IS NONE
フィルターを含める必要があります。これは本質的にレースフリーであり、単一の更新であり、SELECT-UPDATEシーケンスではないため興味深いものです。
園芸品種のセマフォは、データベースの外部で使用できます。キューは(一般的に)低レベルのセマフォよりも操作が簡単です。
これはあなたの状況を単純化しすぎているかもしれませんが、JavaScriptリンクの置き換えだけではどうでしょうか?言い換えると、ユーザーがリンクまたはボタンをクリックすると、JavaScript関数でリクエストがラップされ、リンクがすぐに無効化/「グレー表示」され、テキストが「読み込み中...」または「リクエストの送信中...」などの情報に置き換えられます。同様。それはあなたのために働きますか?
今、あなたは使用する必要があります:
Model.objects.select_for_update().get(foo=bar)