find_or_create и состояние гонки в рельсах, теория и производство - PullRequest
0 голосов
/ 09 февраля 2011

Привет, у меня есть этот кусок кода

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

Но Place.find_or_create_by_latlon получение данных занимает слишком много времени, если выполнено действие создать и иногда в производстве p.place равно нулю.

Как я могу заставить ждатьответ перед выполнением p.save ?спасибо за советы

Ответы [ 3 ]

1 голос
/ 09 февраля 2011

Вы правы, что это условие гонки, и оно часто может быть вызвано людьми, которые дважды щелкают кнопки отправки в формах.То, что вы можете сделать, это вернуться назад, если вы обнаружите ошибку.

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

Есть более элегантные способы сделать это, но основной метод здесь.

0 голосов
/ 12 февраля 2017

Я включил это в задание-помощник, которое повторяет и получает ошибку несколько раз и в конце концов очищается. Лучшее объяснение, которое я нашел, - в блоге здесь . Суть в том, что 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
0 голосов
/ 21 марта 2013

Мне пришлось столкнуться с подобной проблемой.В нашем бэкэнде пользователь создается из токена, если пользователь не существует.ПОСЛЕ ТОГО, КАК пользовательская запись уже создана, отправляется медленный вызов 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

Шаг стратегии состоит в том, чтобы сначала удалить медленный вызов API (в моем случае update_profile_from_facebook) изсоздать метод.Поскольку операция занимает так много времени, вы значительно увеличиваете вероятность дублирования операций вставки, когда вы включаете операцию как часть вызова для создания.

Второй шаг - добавить уникальное ограничение для столбца базы данных вубедитесь, что дубликаты не созданы.

Последний шаг - создание функции, которая будет перехватывать исключение RecordNotUnique в редком случае, когда дубликаты операций вставки отправляются в базу данных.

Это может небыть самым элегантным решением, но оно сработало для нас.

...