如何使用ActiveRecord和Sidekiq

时间:2016-06-02 09:36:30

标签: postgresql activerecord concurrency locking sidekiq

我有一个Sidekiq工作者,它进行API调用,解析返回的json并创建ActiveRecord个对象(产品)。由于产品属于某个品牌,而产品json也包含该品牌的数据,因此工作人员在将产品实际保存到数据库之前会执行以下操作:

  1. 通过检查其唯一的api_id(ID,来自api的id,在db中,此列上有唯一索引)来检查数据库中是否存在该品牌;
  2. 如果确实如此 - 获取其主要ID;
  3. 如果不是 - 创建它并获得其主要身份
  4. 我已经像这样实现了这个:

    def brand_id(brand_json)
      Brand.where(api_id: brand_json[:api_id]).pluck(:id).first.presence ||
      Brand.create!(name: brand_json[:name], api_id: brand_json[:api_id]).id
    end
    

    之后,工作人员创建产品,并将brand_id设置为已获取的ID。

    现在我在考虑以下情况:

    1. 两名工人同时获取属于同一品牌的两种产品的数据,这些产品在数据库中不存在;
    2. 工人1检查品牌并且没有找到它;
    3. 工作人员2检查品牌后不久,但没有找到;
    4. 工人1创造品牌;
    5. 现在工人2会发生什么?我的假设 - 它试图创建品牌,但是在数据库级别出现错误,因为已经存在具有相同api_id的记录? (可能会出现ActiveRecord::RecordNotUnique错误?)

      此外,如何在SidekiqActiveRecord的上下文中处理此案例和此类错误?我应该以某种方式在品牌表上实施表格范围的锁定以防止此类事情发生吗?如果是 - 我将无法同时创建产品,因为在任何给定时间只有一名工人可以访问品牌表,这是创建产品所必需的。

      或许我应该将我的brand_id(brand_json)方法包装在事务中,如下所示:

      def brand_id(brand_json)
        ActiveRecord::Base.transaction do
          Brand.where(api_id: brand_json[:api_id]).pluck(:id).first.presence ||
          Brand.create!(name: brand_json[:name], api_id: brand_json[:api_id]).id
        end
      end
      

      请建议。

1 个答案:

答案 0 :(得分:1)

  1. 将唯一索引约束(可能以多列索引的形式)放在DB中。
  2. 只是尝试创建对象。数据库将阻止您制作多个。
  3. 只允许成功创建初始对象的线程(不发生异常)继续进行额外处理。