2011-05-06 2 views
4

ActiveRecord의 find_or_create_by_*column*을 사용하려고하는데 때때로 Postgres에서 모델을 찾지 못하는 오류를보고 어차피 삽입하려고합니다. 이 테이블을 고유하게 유지하는 것이 중요하므로 Postgres가 내가 그것에 대해 진지하다는 것을 알 수 있도록 :unique => true 속성을 마이그레이션에 추가했습니다.find_or_create 경합 조건

그리고는 실패 :

ActiveRecord::StatementInvalid: PGError: ERROR: duplicate key value violates unique constraint "index_marketo_leads_on_person_id" DETAIL: Key (person_id)=(9968932) already exists. : INSERT INTO "marketo_leads" ("mkt_person_id", "synced_at", "person_updated_at", "person_id") VALUES(NULL, NULL, '2011-05-06 12:57:02.447018', 9968932) RETURNING "id"

내가 그렇게 같은 모델을 가지고 :

두 번째 모델은 Marketo와 전자 메일 서버에 우리의 사용자 계정을 연결하고 기록을 유지하기 위해 사용되는
class User < AR::Base 
    has_one :marketo_lead 

    before_save :update_marketo_lead 

    def update_marketo_lead 
    if marketo_lead 
     if (User.marketo_columns & self.changes.keys).any? 
     marketo_lead.touch(:person_updated_at) 
     end 
    elsif self.id 
     marketo_lead = MarketoLead.find_or_create_by_person_id(:person_updated_at => Time.now, :person_id => self.id) 
    end 
    end 
end 

class MarketoLead 
    belongs_to :user, :foreign_key => 'person_id' 
end 

마지막으로 사용자의 특정 필드가 수정되어 배치 된 백그라운드 작업에서 변경된 레코드를 푸시 할 수 있습니다.

update_marketo_lead은 내가 상상할 수없는 일종의 경쟁 조건 외에도이 콜백에 대한 어떤 이유도 생각할 수 없습니다.

('사용자'사람 '에 기본 키를 공유하는 horribleness 무시하십시오) (사용 레일 2.3.11을, 포스트 그레스 9.0.3)

답변

5
find_or_create가 실행 된 경우, 일치하는 것을

그것 확실히 가능 person_id로 찾을 수 없으므로, create logic이 사용되었지만, find_or_create와 실제 user.save 사이에 또 ​​다른 요청이 트랜잭션 저장을 완료 할 수 있었고 그 시점에서 데이터베이스 제약이이 예외를 발생 시켰습니다. 내가 추천 할 것입니다 무엇

하면 사용자를 저장하려고 어디든지 ...이 실행되어야한다

begin 
    user.save! 
rescue ActiveRecord::StatementInvalid => error 
    @save_retry_count = (@save_retry_count || 5) 
    retry if((@save_retry_count -= 1) > 0) 
    raise error 
end 

참고 StatementInvalid 예외를 잡으려고하고 한정된 횟수까지 (절약을 다시 시도하는 것입니다. 모든 콜백 트랜잭션 내에서 유효성 검사가 진행 중입니다. 트랜잭션

추신 : 귀하의 레일 버전이 트랜잭션을 지원한다고 가정합니다. 레일 3에서는 그 래핑 할 필요가 없습니다! 내부적으로 이미 하나를 사용하고 있기 때문에 트랜잭션이 발생했습니다.

+0

을 올려 보자, 나는 좋은 조언을 감사드립니다 StatementInvalid 오류를 복구하고 유한 재시도 루프를 추가하십시오. 나는'find_or_create' 호출과 같은 예외 처리를 추가했다. 건배! – nessur

+0

내 응용 프로그램에서 모델.ave는 컨트롤러에서 트리거되므로 시작하기 쉽도록 포장하기가 쉽습니다. 사실 나는 save_with_retries (retry_count = 5)라는 메소드를이 모델이 필요로하는 모델에 추가했습니다. –

+0

P.S. ActiveRecord (트랜잭션 문서에 따라)가 이미 저장하고 저장하기 때문에 user.save 주변의 Transaction을 제거했습니다! 내부 거래를 파괴하십시오. –

0

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

내가 문제를 repro 수 없습니다 만, 위의 블라디미르 수정에서 수정 내 시도 수정이기 때문에 아직이를 확인할 수

:

begin 
    user.save! 
rescue ActiveRecord::StatementInvalid => error 
    @save_retry_count = (@save_retry_count || 1) 
    ActiveRecord::Base.connection.reset_pk_sequence!(:user) 
    retry if((@save_retry_count -= 1) >= 0) 
    raise error 
end 

그래서이 첫 번째 시도에 해결되지 않을 경우 나는 user.save 정말 실용적이지 트리거 것들에 대한 내 응용 프로그램을 편집하는 동안 오류가