У меня есть проект Rails 3, работающий поверх PostgreSQL 9.0.
Вариант использования: Пользователи могут запросить подписку на Artists
по имени.Для этого они отправляют список имен в ресурс REST.Если я не могу найти Artist
по имени в локальной коллекции, я обращаюсь к last.fm за информацией о них и кеширую эту информацию локально.Этот процесс может занять некоторое время, поэтому он делегируется фоновому заданию с именем IndexArtistJob
.
Проблема : IndexArtistJob
будет выполняться параллельно.Таким образом, возможно, что два пользователя могут запросить добавление одного и того же Artist
одновременно.Оба пользователя должны добавить Artist
в свою коллекцию, но только один Artist
должен оказаться в локальной базе данных.
Соответствующие части модели 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
определяется следующим образом:
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
в надежде на лучшее.Если конфликт возникает, я хочу, чтобы более медленные работники отказались от работы, которую они сделали в пользу только что вставленного Artist
, и добавили это Artist
к указанному пользователю.
IЯ осознаю тот факт, что валидаторы Rails не могут заменить фактическую проверку целостности данных на уровне базы данных.Для этого я добавил уникальный индекс в поле имени в нижнем регистре таблицы Artist, чтобы обработать это (и использовать для поиска).Теперь, если я правильно понимаю документацию, коллекция ассоциаций AR фиксирует изменения для добавляемого элемента (в данном случае * 1031) и базовой коллекции в транзакции.Но я не могу гарантировать, что Artist
будет добавлено.
Я делаю это правильно?Если так, есть ли лучший способ сделать это?Мне кажется, что структурирование вокруг исключений подчеркивает тот факт, что проблема связана с параллелизмом, и поэтому немного неуловима.