Уникальная проверка Rails не сработала и фоновые задания

#ruby-on-rails #database #postgresql #activerecord #sidekiq

#ruby-on-rails #База данных #postgresql #activerecord #sidekiq

Вопрос:

У меня есть приложение с моделью с именем appointment. В этой модели есть столбец с именем event_uid и проверка, подобная следующей:

 validates :event_uid, uniqueness: true, allow_nil: true
 

Уникальная проверка выполняется только в приложении rails, а не в базе данных (postgresql).

Я использую фоновое задание с sidekiq на heroku для синхронизации некоторых удаленных календарей. Я не уверен, что произошло, но, похоже, я получил несколько записей с повторяющимися event_uid значениями. Они были созданы в ту же секунду.

Я предполагаю, что что-то произошло с рабочими, и по какой-то причине они были вызваны одновременно или очередь заморожена, и когда она вернулась, она дважды выполнила одно и то же задание. Я не понимаю, почему rails пропускает вышеуказанное (возможно, потому, что рабочие, работающие в разных потоках, играют роль?). Я добавил следующую миграцию:

 add_index :appointments, [:event_uid], unique: true
 

В надежде, что это больше не повторится. Хорошо, теперь вопросы:

  • Как вы думаете, этого будет достаточно?
  • Опасно ли разрешать проверки уникальности / присутствия только на уровне приложения, если вы используете create / update с фоновыми заданиями?
  • Есть предположения, что могло заставить рабочих выполнять одно и то же задание более одного раза в одну и ту же секунду?

Ответ №1:

Проверка уникальности Rails долгое время была причиной путаницы.

Когда вы сохраняете экземпляр пользователя, Rails проверяет вашу модель, выполняя запрос SELECT, чтобы узнать, существуют ли какие-либо пользовательские записи с предоставленным электронным письмом. Предполагая, что запись окажется действительной, Rails запустит инструкцию INSERT для сохранения пользователя.

https://thoughtbot.com/blog/the-perils-of-uniqueness-validations

Это означает, что если у вас есть несколько рабочих / потоков, выбирающих одновременно, все они вернут false и вставят запись.

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

Как вы думаете, этого будет достаточно?

Да, добавление индекса — хорошая идея, но теперь вам нужно также обработать ActiveRecord::RecordNotUnique .

Опасно ли разрешать проверки уникальности / присутствия только на уровне приложения, если вы используете create / update с фоновыми заданиями?

Это зависит от приложения, но большую часть времени вы также хотите иметь индекс на уровне БД.

Есть предположения, что могло заставить рабочих выполнять одно и то же задание более одного раза в одну и ту же секунду?

Большинство библиотек фоновых заданий гарантируют, что хотя бы одно задание будет поставлено в очередь, но не точно одно. Ваши задания всегда должны быть идемпотентными (могут выполняться несколько раз). Полезно прочитать это руководство по разработке ActiveJob, особенно часть об идемпотентности.

Ответ №2:

Обычно проверки выполняются в rails только во время обратных вызовов (иногда перед записью в БД), и да, если вы добавили уникальный индекс, это больше не повторится, потому что на этот раз ответственность возьмет на себя БД, поэтому, даже если вы снова столкнетесь с тем же потоком / проблемой, результатом, скорее всего, будет сообщение об ошибкечто вы не можете дублировать это значение индекса.

Учитывая характер валидатора (обычно вызывается во время обратных вызовов и не является потокобезопасным), что означает, что они могут работать в условиях гонки, то, насколько часто это может происходить, зависит от вашего приложения, вы должны всегда добавлять проверку и в БД.

В связи с вашими рабочими я столкнулся с той же проблемой из-за потока повторных попыток Sidekiq несколько месяцев назад, решение состояло в том, чтобы также выполнить проверку на стороне БД и внести исправление для запуска обратного вызова workers / jobs after_commit (не уверен, используете ли вы Sidekiq , но вы всегда можете использовать after_commit обратный вызов, яиспользовал мою работу после выполнения определенной операции над определенным объектом).

Надеюсь, что вышесказанное поможет! 👍