如何确保将单个记录插入轨道

时间:2019-06-13 12:38:53

标签: ruby postgresql concurrency ruby-on-rails-5

在我们的应用程序中,根据配置(一次或多次),用户只能输入一次或多次进入广告系列。

有一个用户模型,一个Campaigns和campaigns_users模型。如果配置是一次性的,则campaigns_users对于一个用户应该只有一条记录。并且如果该广告系列配置了多次,则同一广告系列的同一用户可能会有很多记录。

由于并发处理,记录被插入了两次。我们已经进行了应用程序级别检查,以确保用户是否输入了广告系列。在某些情况下,两个进程会同时运行,并检查对广告系列的订阅,即使配置是一次指定,用户也会被订阅两次。

class User < ApplicationRecord 
    def already_subscribed?(campaign)
      campaign.campaigns_users.find_by(user_id: id).present?
    end
end

在工作中

def perform(user_id, campaign_id)
  campaign = Campaign.find_by(id:  campaign_id)
  user = User.find_by(id: user_id)
  return if campaign.config == 'single' && user.already_subscribed?

  # Other Logics
end

我已经检查了避免在特定情况下输入两次的解决方案,得到的结果是添加 UNIQUE约束。但是在我的情况下,可以根据配置多次输入用户/一次输入用户。避免创建记录的最佳解决方案是什么?

1 个答案:

答案 0 :(得分:0)

我最终使用了Postgres(https://vladmihalcea.com/how-do-postgresql-advisory-locks-work/)的咨询锁。请看看https://github.com/ClosureTree/with_advisory_lock宝石

def perform(user_id, campaign_id)
  campaign = Campaign.find_by(id: campaign_id)
  user = User.find_by(id: user_id)

  # I have used id's as lock name to ensure reducing the time that other threads waiting for accessing this critical section
  CampaignUser.with_advisory_lock("#{user.id}-#{campaign.id}")
    return if campaign.config == 'single' && user.already_subscribed?

    # Other Logics
  end
end