2011-04-29 2 views
0

PostgreSQL 9.0에서 실행되는 Rails 3 프로젝트가 있습니다.ActiveRecord : 작업자 간의 DB 경쟁 처리

사용 사례 : 사용자는 Artists의 이름을 따라달라고 요청할 수 있습니다. 이렇게하기 위해 REST 리소스에 이름 목록을 제출합니다. 로컬 컬렉션에서 Artist의 이름을 찾을 수 없으면 last.fm에 대한 정보를 문의하고 해당 정보를 로컬에 캐시합니다. 이 프로세스는 다소 시간이 걸릴 수 있으므로 IndexArtistJob이라는 백그라운드 작업에 위임됩니다.

문제 : IndexArtistJob이 병렬로 실행됩니다. 따라서 두 명의 사용자가 동일한 Artist을 동시에 추가하도록 요청할 수 있습니다. 두 사용자 모두 해당 컬렉션에 Artist을 추가해야하지만 Artist 중 하나만 로컬 데이터베이스에 저장되어야합니다. Artist 모델의

관련 부분은 다음과 같습니다

class IndexArtistJob < Struct.new(:user_id, :artist_name) 
    def perform 
    user = User.find(user_id) 

    # May return a new, uncommitted Artist model, or an existing, committed one. 
    artist = Artist.lookup(artist_name) 
    return if artist.nil? 

    # Presume the thread is pre-empted here for a long enough time such that 
    # the work done by this worker violates the DB's unique constraint. 
    user.artists << artist 

    rescue ActiveRecord::RecordNotUnique # Lost race, defer to winning model 
    user.artists << Artist.lookup(artist_name) 
    end 
end 

내가 여기서 할 노력하고있어하는 것은 각 작업자가 새로운 Artist을 저지 할 수있다 :로

require 'services/lastfm' 

class Artist < ActiveRecord::Base 
    validates_presence_of :name 
    validates_uniqueness_of :name, :case_sensitive => false 

def self.lookup(name) 
    artist = Artist.find_by_name(name) 
    return artist if not artist.nil? 

    info = LastFM.get_artist_info(name) 
    return if info.nil? 

    # Check local DB again for corrected name. 
    if name.downcase != info.name.downcase 
    artist = Artist.find_by_name(info.name) 
    return artist if not artist.nil? 
    end 

    Artist.new(
     :name => info.name, 
     :image_url => info.image_url, 
     :bio => info.bio 
) 
    end 
end 

IndexArtistJob 클래스 정의 발견, 최선을 희망합니다. 충돌이 발생하면 느린 작업자가 방금 삽입 한 Artist에 대한 작업을 포기하고 지정된 사용자에게 Artist을 추가합니다.

레일스 검사기가 데이터베이스 수준에서 실제 데이터 무결성 검사를 대신 할 수 없다는 사실을 알고 있습니다. 이를 처리하기 위해 아티스트 테이블의 소문자 이름 필드에 고유 인덱스를 추가하여이를 처리했습니다 (검색에 사용). 이제 설명서를 올바르게 이해하면 AR의 연관 수집이 추가되는 항목 (이 경우 Artist)과 트랜잭션의 기본 모음에 대한 변경 사항을 커밋합니다. 그러나 나는 보장 할 수 없다. Artist이 추가 될 것이다.

제대로하고 있습니까? 그렇다면 더 좋은 방법이 있습니까? 예외를 중심으로 구조화하는 것이 문제가 동시성의 하나임을 강조하고 약간의 미묘함을 느끼는 것처럼 느껴집니다.

답변

1

간단한 대기열 메커니즘을 사용할 수있는 것처럼 들립니다. 하는 "프런트 엔드"스레드가 누락 된 아티스트를 발견

  1. 은 "대기"상태로 테이블에 아티스트 이름을 적게한다 (작가의 고유 인덱스가 : 당신은 데이터베이스 테이블을 사용하여이 작업을 수행 할 수 그래서 한번만 일어날 수있는 이름).

  2. 한편 백그라운드 스레드/프로세스는 루프에 앉아 새로운 작업에 대한 테이블을 쿼리 :
    이) =에
    C) 업데이트 아티스트의 상태를 "대기") 거래
    B 시작 상태로 첫 번째 아티스트를 찾을 수 "processing"
    d) 최종 거래

  3. 배경 스레드가 아티스트를 인덱싱합니다. 아무도 상태를 "처리 중"으로 볼 수 있기 때문에 아무도 시도하지 않습니다.

  4. 완료되면 백그라운드 스레드가 테이블에서 아티스트를 삭제합니다.

이 방법을 사용하면 여러 배경 스레드를 실행하여 아티스트 색인 생성의 동시성을 높일 수 있습니다.

또한이 프로세스를 관리하는 beanstalk 같은 것을보십시오. http://railscasts.com/episodes/243-beanstalkd-and-stalker을 참조하십시오.

+0

필자는 큐에 대해 SimpleWorker 또는 delayed_job/Resque를 검토 할 것이라고 언급 했어야합니다. 당신의 솔루션은 대기열을 일종의 뮤텍스로 사용합니다. 이것은 매우 영리합니다. 그러나 나는 바퀴를 다시 발명 할 수 있을지 확신하지 못합니다. –

+0

전적으로 동의합니다 ... 세계에는 충분한 바퀴가 있습니다. – scaganoff

관련 문제