什么可能导致Rails创建重复记录?

时间:2012-03-28 19:10:31

标签: mysql ruby-on-rails-3 nginx duplicates passenger

我们注意到在我们的数据库中的各个表中创建了大量重复记录,但是为什么会发生这种情况却不知所措。有趣的是,虽然记录是重复的(甚至是created_at邮票!),但在我们的用户表上,密码salt和hash在每条记录上都是不同的 - 这让我相信不知何故Rails在某种程度上运行交易/保存操作两次。显然,我们并未在应用程序代码中多次调用savecreate

这种重复似乎不会发生在数据库中保存的每条记录中,我们似乎无法推断出一种模式。用户模型上还有一个validates_uniqueness_of验证(虽然不是表上的唯一键;我们需要清理所有重复项才能做到这一点) - 所以Rails应该在记录时自行停止已存在,但如果请求同时触发这是一个竞争条件。

我们目前在我们的应用服务器(目前其中有2个)上运行Rails 3.2.2,仅次于Passenger 3.0.11 / nginx,并且有一个中央nginx网络服务器向上游发送请求到应用服务器。这种设置可能会导致流程重复或某些原因吗?请求未锁定到一个上游服务器(即,如果一个用户请求包含静态内容的页面,如图像,可能会使用一个或两个应用服务器),这是否重要? (我觉得这是抓住稻草,但我想涵盖所有可能性)

还有什么可能导致这种情况发生?

更新: 例如,今天创建了一个获得重复记录的用户。两者都有created_at 2012-03-28 16:48:11标记,除hashed_passwordsalt之外的所有列都相同。从请求日志中,我可以看到以下内容:

App Server 1:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:47:19 -0400
[2012-03-28 12:47:19] INFO : Processing by ApplyController#create_user as HTML
[2012-03-28 12:47:20] INFO :   Rendered apply/new_user.html.erb within layouts/template (192.8ms)

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML
[2012-03-28 12:48:11] INFO : Redirected to apply/initialize_job_application/3517
[2012-03-28 12:48:11] INFO :  /app/controllers/apply_controller.rb:263:in `block (2 levels) in create_user'

App Server 2:

Started POST "/en/apply/create_user" for 1.2.3.4 at 2012-03-28 12:48:10 -0400
[2012-03-28 12:48:10] INFO : Processing by ApplyController#create_user as HTML

Web服务器:

1.2.3.4 - - [28/Mar/2012:12:48:10 -0400] "POST /en/apply/create_user HTTP/1.1" 499 0 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-"
1.2.3.4 - - [28/Mar/2012:12:48:11 -0400] "POST /en/apply/create_user HTTP/1.1" 302 147 "en/apply/create_user" "Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0)" "-"

因此创建动作被击中三次(可能由于错误而第一次返回到表单),并且每个服务器上至少一次。后两者都由网络服务器注册为单独的请求,但第一个获取状态代码499 Client Closed Request(根据维基百科的nginx扩展名),第二个获得302预期。 499可能会引起问题吗?

2 个答案:

答案 0 :(得分:5)

有两种可能性浮现在脑海中。

当用作负载均衡器时,第一个是Nginx的奇怪(并且反对RFC)行为。它将针对下一个后端重试任何失败的请求。 RFC仅允许安全方法(例如GETHEAD)。这样做的结果是,如果您的nginx因某种原因认为请求失败,则可能是它被重新发送到下一个服务器。如果两个服务器完成了他们的事务,则会有重复的记录。从你的webservers日志(以及Nginx用来表示用户在浏览器中点击中止的499状态代码)来看,这看起来是最可能的原因。

第二种可能性是您的用户双击发送按钮。通过正确的时间安排,他们的浏览器几乎可以同时发送两个完整的请求。

要确保您的用户记录确实是唯一的,您应该在数据库上创建唯一索引。然后实际确保这些(尽管与ActiveRecord检查相比,错误消息更糟。因此,您应该始终在数据库模式和模型上定义唯一性约束。

此外,您可以考虑用更符合标准的负载均衡器替换您的前端nginx。我建议haproxy

答案 1 :(得分:0)

这看起来像是一场竞争。确保在请求之间锁定。很可能会偶尔发生一两个请求的重复。在没有交易的情况下交换物品时也会发生同样的情况,因此请确保您的请求之间没有竞争。