2011-02-08 5 views
0

안녕 나는find_or_create 난간, 이론에서 경쟁 조건 및 생산

class Place < ActiveRecord::Base 
    def self.find_or_create_by_latlon(lat, lon) 
    place_id = call_external_webapi 
    result = Place.where(:place_id => place_id).limit(1) 
    result = Place.create(:place_id => place_id, ...) if result.empty? #! 
    result 
    end 
end 

가 그럼 난 다른 모델이나 컨트롤러에서 할 싶습니다이 코드 조각을했습니다

p = Post.new 
p.place = Place.find_or_create_by_latlon(XXXXX, YYYYY) # race-condition 
p.save 

그러나 작업 실행이 인 경우이 데이터를 가져 오는 데 너무 많은 시간이 걸리며을 만들고 때로는 프로덕션에서 p.place을 nil로 만듭니다.

실행하기 전에 어떻게 응답을 기다려야합니까? p.save? 귀하의 조언을 보내 주셔서 감사합니다

+0

여기에 비슷한 질문이 있습니다. http : // stackoverflow.com/questions/6768647/rails-3-potential-race-condition – jim

답변

1

이것이 경쟁 조건이며 양식의 버튼을 두 번 클릭하는 사람들이 종종 유발할 수 있습니다. 오류가 발생하면 루프 백을 수행 할 수 있습니다.

result = Place.find_by_place_id(...) || 
    Place.create(...) || 
    Place.find_by_place_id(...) 

더 세련된 방법이 있지만 기본 방법은 여기에 나와 있습니다.

+0

답장을 보내 주셔서 감사합니다. 동일 인 지 모르겠습니다. 왜냐하면 place_id를 알기 위해 외부 서비스에 연락해야하기 때문입니다. timeout에 도달하면 timeout과 default 값을 설정하고 싶습니다. – grigio

0

비슷한 문제를 처리해야했습니다. 백엔드에서는 사용자가 존재하지 않으면 토큰에서 사용자를 만듭니다. 사용자 레코드가 이미 생성 된 후 느린 API 호출이 전송되어 사용자 정보가 업데이트됩니다.

def self.find_or_create_by_facebook_id(facebook_id) 
    User.find_by_facebook_id(facebook_id) || User.create(facebook_id: facebook_id) 
rescue ActiveRecord::RecordNotUnique => e 
    User.find_by_facebook_id(facebook_id) 
end 

def self.find_by_token(token) 
    facebook_id = get_facebook_id_from_token(token) 

    user = User.find_or_create_by_facebook_id(facebook_id) 

    if user.unregistered? 
    user.update_profile_from_facebook 
    user.mark_as_registered 
    user.save 
    end 

    return user 
end 

전략의 단계는 먼저 만드는 방법에서 (내 경우 update_profile_from_facebook에서) 느린 API 호출을 제거하는 것입니다. 작업이 너무 오래 걸리기 때문에 작업을 create 호출의 일부로 포함하면 삽입 작업이 중복 될 확률이 현저하게 증가합니다.

두 번째 단계는 데이터베이스 열에 고유 제한 조건을 추가하여 중복이 작성되지 않도록합니다.

마지막 단계는 중복 삽입 작업이 데이터베이스로 전송되는 드문 경우에 RecordNotUnique 예외를 catch하는 함수를 만드는 것입니다.

이것은 가장 우아한 해결책은 아니지만 그것은 우리를 위해 일했습니다.

0

재 시도하고 반복적으로 오류가 발생하여 결국 자체를 지 웁니다. 나는 다른 요청으로부터 그것의 경쟁 조건을 확신하지 못한다. 또는 정말로 드물 것이며 한 번 또는 두 번만 발생하지만 내가 보는 것처럼 11 번 연속으로 발생하지는 않는다. 내가 찾은 가장 좋은 설명은 블로그 게시물 here에 있습니다. 요점은 postgres가 어떻게 든 엉망이되는 기본 키를 증가시키기 위해 내부적으로 저장된 값을 유지한다는 것입니다. 기본 키를 설정하고 증분 된 값만 사용하는 것이 아니기 때문에 이것은 사실입니다. 어쩌면 이것이 이렇게 잘랐을 수도 있습니다. 위의 링크에있는 의견의 해결책은 전화로 나타나는 것 같습니다. ActiveRecord::Base.connection.reset_pk_sequence!(table_name)

문제를 재현 할 수 없기 때문에 아직 확인할 수 없지만 오류를 발견하고 재설정을 시도하면 해결할 수 있습니다. 재시도 테이블. 이 문제가 해결되지 않으면 오류가 발생하여 곧 알아볼 것입니다.

begin 
    result = Place.where(:place_id => place_id).limit(1) 
    result = Place.create(:place_id => place_id, ...) if result.empty? #! 
rescue ActiveRecord::StatementInvalid => error 
    @save_retry_count = (@save_retry_count || 1) 
    ActiveRecord::Base.connection.reset_pk_sequence!(:place) 
    retry if((@save_retry_count -= 1) >= 0) 
    raise error 
end 
관련 문제