AppEngine:レコード作成時のデータストアの一貫性の維持
-
22-08-2019 - |
質問
小さなジレンマに遭遇しました!vote というハンドラーがあります。これが呼び出されると、ユーザーの投票がユーザーが選択したものに設定されます。彼らが以前に選択したオプションを記憶するために、現在の投票がどのように設定されているかの詳細を示す VoteRecord オプションを保存します。
もちろん、初めて投票するときは、オブジェクトを作成して保存する必要があります。ただし、連続した投票では、既存の VoteRecord の値が変更されるだけです。しかし、彼は問題を抱えています。状況によっては、2 つの VoteRecord が作成されることがあります。それはまれですが (これまでに確認した 500 票のうち 1 回だけ発生しました)、発生した場合は依然として問題があります。
この問題は、2 つの別個のハンドラーがどちらも本質的にこれを行うために発生します。
query = VoteRecord.all().filter('user =', session.user).filter('poll =', poll)
if query.count(1) > 0:
vote = query[0]
poll.votes[vote.option] -= 1
poll.votes[option] += 1
poll.put()
vote.option = option
vote.updated = datetime.now()
vote.put()
else:
vote = VoteRecord()
vote.user = session.user
vote.poll = poll
vote.option = option
vote.put()
poll.votes[option] += 1
poll.put()
session.user.votes += 1
session.user.xp += 3
session.user.put()
incr('votes')
私の質問は次のとおりです。リクエストが失われず、リクエストが 2 つの VoteRecord オブジェクトを作成しないようにしながら、これらのリクエストを処理する最も効果的かつ最速の方法は何ですか?
解決
問題はこの部分です:
if vote.count(1) == 0:
obj = VoteRecord()
obj.user = user
obj.option = option
obj.put()
トランザクションがなければ、コードは 2 つのインタープリター インスタンスで次の順序で実行される可能性があります。
if vote.count(1) == 0:
obj = VoteRecord()
obj.user = user
if vote.count(1) == 0:
obj = VoteRecord()
obj.user = user
obj.option = option
obj.put()
obj.option = option
obj.put()
あるいはそれらの奇妙な組み合わせ。問題は、put が発生する前に count テストが再度実行されるため、2 番目のスレッドが条件の 2 番目の部分ではなく最初の部分を通過することです。
この問題は、コードを関数に入れて次を使用することで修正できます。
db.run_in_transaction()
関数を実行します。
問題は、トランザクションに含める必要がある決定ロジックのクエリによって返されるオブジェクトの数に依存しているようです。Google I/O の講演を読んだり、グループを見たりすると、これが推奨されていないことがわかります。これは、クエリをトランザクション化できないためです。代わりに、カウントをエンティティ値としてどこかに保存し、クエリを実行する必要があります。 外 トランザクション関数のキーを取得し、そのエンティティのキーをトランザクション関数に渡します。
次に、エンティティ プロパティをチェックするトランザクション関数の例を示します。キーがパラメータとして渡されます。
def checkAndLockPage(pageKey):
page = db.get(pageKey)
if page.locked:
return False
else:
page.locked = True
page.put()
return True
このエンティティをロックできるのは一度に 1 人のユーザーだけであり、ロックが重複することはありません。
他のヒント
これを行う最も簡単な方法は、あなたの投票オブジェクトのキー名を使用し、Model.get_or_insertを使用することです。投票後にそれを命名することは良いアイデアです - - まず、あなたのキー名の命名スキームを思い付くし、次にフェッチまたは関連するエンティティを作成するget_or_insertを行います:
vote = VoteRecord.get_or_insert(pollname, parent=session.user, user=session.user, poll=poll, option=option)
if vote.option != option:
# Record already existed; we need to update it
vote.option = option
vote.put()