[RUBY-ON-RAILS] 어떻게 레일 응용 프로그램에서 경쟁 조건을 피할 수 있습니까?
RUBY-ON-RAILS어떻게 레일 응용 프로그램에서 경쟁 조건을 피할 수 있습니까?
나는 사용자가 코스의 세트에 출석을 등록 할 수있는 정말 간단한 레일 응용 프로그램이 있습니다. 다음과 같이 액티브 모델은 다음과 같습니다 :
class Course < ActiveRecord::Base
has_many :scheduled_runs
...
end
class ScheduledRun < ActiveRecord::Base
belongs_to :course
has_many :attendances
has_many :attendees, :through => :attendances
...
end
class Attendance < ActiveRecord::Base
belongs_to :user
belongs_to :scheduled_run, :counter_cache => true
...
end
class User < ActiveRecord::Base
has_many :attendances
has_many :registered_courses, :through => :attendances, :source => :scheduled_run
end
ScheduledRun 인스턴스를 사용할 장소의 유한 수 있고, 한계에 도달하면 더 이상 출석이 인정 될 수 없다.
def full?
attendances_count == capacity
end
attendances_count는 특정 ScheduledRun 레코드 생성 출석 연결 수를 들고 카운터 캐시 열입니다.
내 문제는 내가 완전히 하나 또는 더 많은 사람들이 동시에 코스에서 사용 가능한 마지막 장소를 등록 할 때 경쟁 조건이 발생하지 않도록하는 올바른 방법을 모르는 것입니다.
내 출석 컨트롤러는 다음과 같다 :
class AttendancesController < ApplicationController
before_filter :load_scheduled_run
before_filter :load_user, :only => :create
def new
@user = User.new
end
def create
unless @user.valid?
render :action => 'new'
end
@attendance = @user.attendances.build(:scheduled_run_id => params[:scheduled_run_id])
if @attendance.save
flash[:notice] = "Successfully created attendance."
redirect_to root_url
else
render :action => 'new'
end
end
protected
def load_scheduled_run
@run = ScheduledRun.find(params[:scheduled_run_id])
end
def load_user
@user = User.create_new_or_load_existing(params[:user])
end
end
당신이 볼 수 있듯이, 그것은 ScheduledRun의 인스턴스가 이미 용량에 도달 한 고려하지 않습니다.
이것에 어떤 도움을 크게 감상 할 수있다.
최신 정보
나는 이것이이 경우 낙관적 잠금을 수행 할 수있는 올바른 방법 인 경우 확실하지 않다,하지만 여기에 내가 무슨 짓을했는지 :
나는 ScheduledRuns 테이블에 두 개의 열을 추가 -
t.integer :attendances_count, :default => 0
t.integer :lock_version, :default => 0
또한 ScheduledRun 모델 방법을 추가 :
def attend(user)
attendance = self.attendances.build(:user_id => user.id)
attendance.save
rescue ActiveRecord::StaleObjectError
self.reload!
retry unless full?
end
출석 모델이 저장되면, 액티브는 계속 작동하고 ScheduledRun 모델에 대한 카운터 캐시 열을 업데이트합니다. 여기에 이런 로그 출력을 보여주는입니다 -
ScheduledRun Load (0.2ms) SELECT * FROM `scheduled_runs` WHERE (`scheduled_runs`.`id` = 113338481) ORDER BY date DESC
Attendance Create (0.2ms) INSERT INTO `attendances` (`created_at`, `scheduled_run_id`, `updated_at`, `user_id`) VALUES('2010-06-15 10:16:43', 113338481, '2010-06-15 10:16:43', 350162832)
ScheduledRun Update (0.2ms) UPDATE `scheduled_runs` SET `lock_version` = COALESCE(`lock_version`, 0) + 1, `attendances_count` = COALESCE(`attendances_count`, 0) + 1 WHERE (`id` = 113338481)
후속 업데이트는 새로운 출석 모델이 저장되기 전에 ScheduledRun 모델에 발생하는 경우, 이것은 StaleObjectError 예외를 유발한다. 용량이 아직 도달하지 않은 경우 어떤 시점에서, 모든 것은 다시 시도됩니다.
업데이트 # 2
SheduledRun 개체의 업데이트 참석 방법은 여기 @ 켄의 반응에서됩니다에 따라 :
# creates a new attendee on a course
def attend(user)
ScheduledRun.transaction do
begin
attendance = self.attendances.build(:user_id => user.id)
self.touch # force parent object to update its lock version
attendance.save # as child object creation in hm association skips locking mechanism
rescue ActiveRecord::StaleObjectError
self.reload!
retry unless full?
end
end
end
해결법
-
==============================
1.낙관적 잠금 길을 가야하는 것입니다,하지만 당신은 이미 눈치 챘을 수도로 has_many 협회에서 자식 객체 생성이 잠금 장치를 건너 뛰고 있기 때문에, 당신의 코드는, 액티브 :: StaleObjectError를 제기하지 않습니다. 다음 SQL를 살펴 보자 :
낙관적 잠금 길을 가야하는 것입니다,하지만 당신은 이미 눈치 챘을 수도로 has_many 협회에서 자식 객체 생성이 잠금 장치를 건너 뛰고 있기 때문에, 당신의 코드는, 액티브 :: StaleObjectError를 제기하지 않습니다. 다음 SQL를 살펴 보자 :
UPDATE `scheduled_runs` SET `lock_version` = COALESCE(`lock_version`, 0) + 1, `attendances_count` = COALESCE(`attendances_count`, 0) + 1 WHERE (`id` = 113338481)
상위 개체의 속성을 업데이트 할 때, 당신은 일반적으로 다음과 같은 SQL을 대신 참조 :
UPDATE `scheduled_runs` SET `updated_at` = '2010-07-23 10:44:19', `lock_version` = 2 WHERE id = 113338481 AND `lock_version` = 1
구현 방법 낙관적 잠금 위의 문 쇼 : 공지 사항 WHERE 절에 lock_version = 1. 이후, 경쟁 조건이 발생하면 동시 프로세스는이 정확한 쿼리를 실행하려고하지만, 첫 번째는 원자 2에 lock_version를 업데이트하기 때문에 단지 첫 번째, 성공, 이후 프로세스는 기록과 인상 액티브 :: StaleObjectError을 찾을 수 없게됩니다 같은 기록은 더 이상 lock_version = 1이 없습니다.
따라서, 귀하의 경우, 가능한 해결 방법과 같이, 당신이 작성하기 전에 / 부모의 권리를 만지지 자식 개체를 파괴하는 것입니다 :
def attend(user) self.touch # Assuming you have updated_at column attendance = self.attendances.create(:user_id => user.id) rescue ActiveRecord::StaleObjectError #...do something... end
엄격하게 경쟁 조건을 방지하는 것은 아닙니다,하지만 실제로는 대부분의 경우에 작동합니다.
-
==============================
2.@ run.full 경우 당신은 테스트하지 않아도 ??
@ run.full 경우 당신은 테스트하지 않아도 ??
def create unless @user.valid? || @run.full? render :action => 'new' end # ... end
편집하다
당신은 검증을 무엇을 같은 추가하는 경우 :
class Attendance < ActiveRecord::Base validate :validates_scheduled_run def scheduled_run errors.add_to_base("Error message") if self.scheduled_run.full? end end
관련 scheduled_run가 가득 찬 경우는 @attendance를 저장하지 않습니다.
이 코드를 테스트하지 않았습니다 ...하지만 난 괜찮아 생각합니다.
from https://stackoverflow.com/questions/3037029/how-do-i-avoid-a-race-condition-in-my-rails-app by cc-by-sa and MIT license
'RUBY-ON-RAILS' 카테고리의 다른 글
[RUBY-ON-RAILS] 방법은 OpenSSL : SSL 없애 :: SSLError (0) | 2020.03.02 |
---|---|
[RUBY-ON-RAILS] 개발은 4 레일에 ActionMailer 메일을 발송하지 (0) | 2020.03.02 |
[RUBY-ON-RAILS] 유효성 수용은 항상 실패 (0) | 2020.03.02 |
[RUBY-ON-RAILS] 액티브가 포함되어 있습니다. 포함 된 열을 지정합니다 (0) | 2020.03.02 |
[RUBY-ON-RAILS] 정의되지 않은 방법 attr_accessible (0) | 2020.03.02 |