find_or_create условия гонки - PullRequest
5 голосов
/ 07 мая 2011

Я пытаюсь использовать ActiveRecord find_or_create_by_*column*, но я получаю ошибки от Postgres, которые сообщают мне, что иногда не удается найти модель, и в любом случае пытается вставить ее. Очень важно, чтобы я сохранял эту таблицу уникальной, поэтому я добавил атрибут :unique => true к ее миграции, чтобы Postgres знал, что я серьезно к этому отношусь.

И, сбой:

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"

У меня есть такие модели:

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

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

Я не могу думать ни о какой причине для этого обратного вызова, update_marketo_lead, чтобы потерпеть неудачу, кроме какого-то состояния гонки, которое я не могу себе вообразить.

(пожалуйста, не обращайте внимания на чудовищность «пользователя», разделяющего первичный ключ с «человеком») (с использованием Rails 2.3.11, Postgres 9.0.3)

Ответы [ 2 ]

5 голосов
/ 07 мая 2011

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

Я бы порекомендовал перехватить исключение StatementInvalid и повторить попытку сохранения (до конечного числа раз ...

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

Обратите внимание, что это должно выполняться везде, где выпопробуйте сохранить пользователя. Все обратные вызовы и проверки выполняются в рамках сохранения! транзакция

PS Я предполагаю, что ваша версия rails поддерживает транзакции :) В Rails 3 нет необходимости в переносе сохранения!в транзакции, потому что он уже использует один внутренне

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

Я нажимаю на это в задании-помощнике, которое повторяет и получает ошибку несколько раз и в конце концов очищается. Я не уверен, что это состояние гонки из-за другого запроса, иначе это будет действительно редко и случится только один или два раза, но не 11 раз подряд, как я вижу. Лучшее объяснение, которое я нашел, - в блоге здесь . Суть в том, что postgres сохраняет внутренне хранимое значение для увеличения первичного ключа, который каким-то образом запутался. Это звучит правдоподобно для меня, потому что я устанавливаю первичный ключ, а не просто использую увеличенное значение, так что, возможно, именно так это и произошло. Решение из комментариев в ссылке выше, кажется, чтобы позвонить ActiveRecord::Base.connection.reset_pk_sequence!(table_name)

Я пока не могу проверить это, потому что не могу воспроизвести проблему, но моя попытка исправления, модифицированная из исправления Владимира выше:

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

Так что, если это не исправит это с первой попытки, я увижу ошибку

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...