単純な競合状態があります。人々が写真に投票できるウェブサイトを持っていますが、最大 10 票が許可されています。
ユーザーが投票を送信すると、その特定の写真の写真テーブルの num_votes 列を更新します。投票数を簡単に調べるためにこれを行います。
vote.save と num_votes の更新が同じトランザクションで行われるようにするにはどうすればよいですか?
ありがとう!
単純な競合状態があります。人々が写真に投票できるウェブサイトを持っていますが、最大 10 票が許可されています。
ユーザーが投票を送信すると、その特定の写真の写真テーブルの num_votes 列を更新します。投票数を簡単に調べるためにこれを行います。
vote.save と num_votes の更新が同じトランザクションで行われるようにするにはどうすればよいですか?
ありがとう!
これを実現するには、ある種のロックを使用する必要があります。基本的には、楽観的/悲観的なレール ロックと、いくつかの外部ロック バックエンド (Redis::Lock など) の 3 つのオプションがあります。
ここで高いパフォーマンスが実際に当てはまらない場合、私は個人的に悲観的ロックを使用します
photo = Photo.find(photo_id)
photo.with_lock do
photo.num_votes += 1
photo.save!
end
また、増分 num_votes をラップして 1 つのトランザクションに保存することに固執しても、競合状態は解決されないことも指摘しておく必要があります。ほとんどの RDBMS は、デフォルトで読み取りコミット モードで動作します。これは、そのような競合状態を防ぎません。
単純な競合状態の場合は、競合状態として解決する必要があります。いくつかのロック機構を使用してみてください。Redis は準備万端です: ruby のための redis のロック
RedisLocker.new('vote_#{@photo.id}').run! { @photo.vote }
# ... photo model
def vote
if num_votes <= 10
self.num_votes += 1
save
end
end
これには明示的なロックは必要ありません
Photo.where(:id => photo_id).where('num_votes < 10').update_all('num_votes = num_votes+ 1')
その写真の投票数を更新しますが、投票数が 10 未満の場合のみです。の戻り値をチェックしてupdate_all
、何かが実際に更新されたかどうかを確認できます。戻り値は、更新された行の数です。更新が失敗した場合は、投票を作成しないでください (または、既に投票を作成している場合は、トランザクションをロールバックします)。
楽観的ロックは、同様の手法を使用して同時更新の試行を検出します。更新に条件を設定して、誰かがあなたの前に忍び込んでも何も起こらないようにし、更新された行の数をチェックします。
Rails/Postgres はトランザクションをサポートしています。任意の ActiveRecord モデルで簡単に宣言できます。
Photo.transaction do
Vote.create(:whatever)
Photo.votes = thing
Photo.save!
end
トランザクション ブロック中に例外が発生した場合 (たとえば、.save!
無効なモデルを呼び出すことによって)、トランザクションはロールバックされ、そこで発生するはずだったデータベースの変更はコミットされません (この場合、Vote レコードはコミットされません)。挿入されます)。もちろん、例外をレスキューして処理する必要があります。
ちなみに、関連するオブジェクトの数をレコードに保存して簡単に検索できるようにすることは、カウンター キャッシュと呼ばれる非常に一般的なパターンであり、Rails はそれらもサポートしていnum_votes
ますphotos.votes_count
。ただし、必須ではありません)。ただし、トランザクションが制限を超えていないことを確認する必要がある場合もあります。