查找或创建竞争条件

发布于 2024-11-05 21:34:17 字数 1263 浏览 2 评论 0原文

我正在尝试使用 ActiveRecord 的 find_or_create_by_*column*,但我从 Postgres 收到错误,让我知道它有时无法找到模型,并尝试插入一个模型。保持该表的唯一性非常重要,因此我添加了 :unique =>; true 属性添加到它的迁移中,这样 Postgres 就会知道我是认真对待它的。

并且,失败:

ActiveRecord::StatementInvalid:PGError:错误:重复的键值违反了唯一约束“index_marketo_leads_on_person_id”详细信息:键(person_id)=(9968932)已经存在。 :插入“marketo_leads”(“mkt_person_id”,“synced_at”,“person_updated_at”,“person_id”)VALUES(NULL,NULL,'2011-05-06 12:57:02.447018',9968932)返回“id”

我有这样的模型:

class User < AR::Base
  has_one :marketo_lead

  before_save :update_marketo_lead

  def update_marketo_lead
    if marketo_lead
      if (User.marketo_columns & self.changes.keys).any?  
        marketo_lead.touch(:person_updated_at) 
      end
    elsif self.id
      marketo_lead = MarketoLead.find_or_create_by_person_id(:person_updated_at => Time.now, :person_id => self.id) 
    end
  end
end

class MarketoLead
  belongs_to :user, :foreign_key => 'person_id'
end

第二个模型用于将我们的用户帐户链接到 Marketo 电子邮件服务器,并记录用户的某些字段上次修改的时间,以便我们可以在批量后台推送更改的记录任务。

除了某种我无法想象的竞争条件之外,我想不出此回调 update_marketo_lead 失败的任何原因。

(请忽略“用户”与“人”共享主键的可怕之处) (使用 Rails 2.3.11、Postgres 9.0.3)

I'm trying to use ActiveRecord's find_or_create_by_*column*, but I'm getting errors from Postgres letting me know that it occasionally fails to find the model, and tries to insert one anyways. It's really important that I keep this table unique, so I added a :unique => true attribute to its migration, so that Postgres would know that I was serious about it.

And, fail:

ActiveRecord::StatementInvalid: PGError: ERROR: duplicate key value violates unique constraint "index_marketo_leads_on_person_id" DETAIL: Key (person_id)=(9968932) already exists. : INSERT INTO "marketo_leads" ("mkt_person_id", "synced_at", "person_updated_at", "person_id") VALUES(NULL, NULL, '2011-05-06 12:57:02.447018', 9968932) RETURNING "id"

I have models like so:

class User < AR::Base
  has_one :marketo_lead

  before_save :update_marketo_lead

  def update_marketo_lead
    if marketo_lead
      if (User.marketo_columns & self.changes.keys).any?  
        marketo_lead.touch(:person_updated_at) 
      end
    elsif self.id
      marketo_lead = MarketoLead.find_or_create_by_person_id(:person_updated_at => Time.now, :person_id => self.id) 
    end
  end
end

class MarketoLead
  belongs_to :user, :foreign_key => 'person_id'
end

The second model is used for linking our users accounts to the Marketo email server, and keeping a record of the last time certain fields of the user was modified, so that we can push changed records in batched background tasks.

I can't think of any reason for this callback, update_marketo_lead to fail, other than some kind of race condition that I can't quite imagine.

(please ignore the horribleness of 'user' sharing a primary key with 'person')
(using Rails 2.3.11, Postgres 9.0.3)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

一梦浮鱼 2024-11-12 21:34:17

很可能在执行 find_or_create 时,找不到匹配的 person_id,因此使用了创建逻辑,但是有可能在 find_or_create 和实际 user.save 之间,另一个请求成功完成了保存事务,此时您的数据库约束导致了此异常。

我建议是捕获 StatementInvalid 异常并重试保存(最多有限次数...

begin
   user.save!
rescue ActiveRecord::StatementInvalid => error
  @save_retry_count =  (@save_retry_count || 5)
  retry if( (@save_retry_count -= 1) > 0 )
  raise error
end

请注意,无论您尝试保存用户,都应该执行此操作。所有回调和验证都发生在 save! 事务中

P.S. 我假设你的 Rails 版本支持事务:) 在 Rails 3 中,不需要换行保存!在事务中,因为它已经在内部使用了一个

Its quite possible that when find_or_create was executed, matching person_id was not found, so create logic was used, however its possible that between find_or_create and actual user.save, another request managed to complete save transaction and at that point your Database constraint caused this exception.

What I would recommend is to catch StatementInvalid exception and to retry saving(up to a finite number of times...

begin
   user.save!
rescue ActiveRecord::StatementInvalid => error
  @save_retry_count =  (@save_retry_count || 5)
  retry if( (@save_retry_count -= 1) > 0 )
  raise error
end

Note this should be executed wherever you try to save the user. All callbacks and validations are happening within save! transaction

P.S. Im assuming your version of rails supports transactions :) In Rails 3 its unnecessary to wrap save! in transaction because it already uses one internally

入怼 2024-11-12 21:34:17

我在一个伙伴作业中遇到了这个问题,该作业会重试并反复出现错误,并最终自行清除。我不相信这是来自另一个请求的竞争条件,或者这种情况非常罕见,只会发生一两次,但不会像我看到的那样连续发生 11 次。我找到的最好的解释是在博客文章此处。要点是 postgres 保留一个内部存储的值,用于增加主键,但主键会以某种方式弄乱。这对我来说是正确的,因为我正在设置主键,而不仅仅是使用递增的值,所以也许这就是它的出现方式。上面链接中评论中的解决方案似乎是调用 ActiveRecord::Base.connection.reset_pk_sequence!(table_name)

我还无法验证这一点,因为我无法重现该问题,但是我尝试的修复,从上面弗拉基米尔的修复修改而来是:

begin
   user.save!
rescue ActiveRecord::StatementInvalid => error
   @save_retry_count =  (@save_retry_count || 1)
   ActiveRecord::Base.connection.reset_pk_sequence!(:user)
   retry if( (@save_retry_count -= 1) >= 0 )
   raise error
end

所以如果第一次尝试没有修复它,我会看到一个错误

I'm hitting this inside a sidekick job that retries and gets the error repeatedly and eventually clears itself. I'm not convinced its a race condition from another request or it would be really rare and only happen once or twice but not 11 consecutive times like I'm seeing. The best explanation I've found is on a blog post here. The gist is that postgres keeps an internally stored value for incrementing the primary key that gets messed up somehow. This rings true for me because I'm setting the primary key and not just using an incremented value so maybe that's how this cropped up. The solution from the comments in the link above appears to be to call ActiveRecord::Base.connection.reset_pk_sequence!(table_name)

I can't verify this yet because I couldn't repro the issue, but my attempted fix, modified from Vladimir's fix above is:

begin
   user.save!
rescue ActiveRecord::StatementInvalid => error
   @save_retry_count =  (@save_retry_count || 1)
   ActiveRecord::Base.connection.reset_pk_sequence!(:user)
   retry if( (@save_retry_count -= 1) >= 0 )
   raise error
end

So if this doesn't fix it on the first try I'll see an error raised

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文