#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
обратный вызов, яиспользовал мою работу после выполнения определенной операции над определенным объектом).
Надеюсь, что вышесказанное поможет! 👍