Rails 唯一验证无效且后台作业

Rails unique validation didn't worked and background jobs

我有一个模型名为 appointment 的应用程序。在此模型上,有一个名为 event_uid 的列和如下验证:

validates :event_uid, uniqueness: true, allow_nil: true

唯一验证仅在 rails 应用程序中而不在数据库 (postgresql) 中。

我在 heroku 上使用带有 sidekiq 的后台作业来同步一些远程日历。我不确定发生了什么,但似乎我得到了多个具有重复 event_uid 值的记录。它们是在同一秒内创建的。

我的猜测是工作人员发生了一些事情并且由于某种原因他们同时被调用或者队列被冻结并且当它返回时它运行相同的工作两次。我不明白为什么 rails 让上面的通过(也许是因为不同线程上的工人 运行 起作用了?)。我添加了以下迁移:

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

希望以后不要再发生了。好的,现在是问题:

通常,验证仅在回调期间在 rails 中进行(有时 before_commit 数据库上的记录),是的,如果您添加了唯一索引,这将不会再次发生,因为数据库会这次负责,所以即使您再次 运行 进入相同的 flow/issue 结果也可能是一个错误,表明您不能复制该索引值。

鉴于验证器的性质(通常在回调期间调用并且不是线程安全的)意味着它们可以 运行 进入竞争条件,这种情况发生的频率取决于您的应用程序,您应该始终在数据库上添加验证。

与您的工作人员有关,由于几个月前 Sidekiq 的重试流程,我 运行 遇到了同样的问题,解决方案也是在数据库端进行验证并进行修复到 运行 workers/jobs after_commit 回调(不确定你是否使用 Sidekiq,但你总是可以使用 after_commit 回调,我在之后使用我的工作特定操作发生在特定对象上)。

希望以上内容对您有所帮助!

Rails 唯一性验证长期以来一直是混淆的原因。

When you persist a user instance, Rails will validate your model by running a SELECT query to see if any user records already exist with the provided email. Assuming the record proves to be valid, Rails will run the INSERT statement to persist the user.

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

这意味着,如果您同时选择多个工作线程/线程,它们都会 return false 并插入记录。

大多数时候希望在数据库级别上有一个索引来避免这些竞争条件。但是,您现在还需要处理任何 ActiveRecord::RecordNotUnique 异常。

What do you think, will this be enough?

是的,添加索引是个好主意,但现在您还需要处理 ActiveRecord::RecordNotUnique

Is it dangerous to allow unique / presence validations to exist only on application level if you are using create / update with background jobs?

这取决于应用程序,但大多数时候您也希望在数据库级别拥有索引。

Any guess what could have caused the workers to run the same job more than one and exactly the same second?

大多数后台作业库只保证至少有一个作业入队,但不保证只有一个。你的工作应该总是 idempotent (can run several times). A good read is this guide about ActiveJob design, especially the part about idempotency.