Rails 3.2频繁postgres准备语句已经存在错误

时间:2014-06-27 17:56:23

标签: ruby-on-rails postgresql heroku ruby-on-rails-3.2 prepared-statement

我一直在挖掘stackoverflow,试图找到其他人,这些准备好的语句已经存在错误。

在大多数情况下,使用after / before fork正确配置unicorn可以解决这些问题。

但是在我的情况下,我们仍然会遇到错误:

ActiveRecord::StatementInvalid: PG::Error: ERROR: prepared statement "a495" already exists: INSERT INTO "user_logins" ("account_id", "created_at", "ip_address", "user_agent", "user_id") VALUES ($1, $2, $3, $4, $5) RETURNING "id"

此错误会在我们的应用中的不同区域引发,但似乎总是具有相同的声明编号' a495'。

我们在轨道3.2.17,使用postgres,我们在heroku。

我真的不知道为什么会这样,但它现在开始更频繁地发生。

非常感谢任何帮助。

在rails堆栈跟踪中,.prepare调用中抛出此错误。我很困惑,因为它检查了语句集合中的sql键。如果它不存在则准备新的....但是当试图准备它时,它会抛出错误。

def prepare_statement(sql)
  sql_key = sql_key(sql)
  unless @statements.key? sql_key
    nextkey = @statements.next_key
    @connection.prepare nextkey, sql
    @statements[sql_key] = nextkey
  end
  @statements[sql_key]
end

3 个答案:

答案 0 :(得分:30)

我们遇到了同样的问题,并进行了非常彻底的调查。我们得出结论,在我们的例子中,这个错误是由Rack::Timeout引起的,它偶尔会在新语句创建之后但在Rails端更新计数器之前中断代码执行。然后,下一个准备好的语句尝试使用相同的名称(例如a494),并发生冲突。

我认为Rails没有正确实现预准备语句。他们应该使用GUID,而不是使用增加的计数器(a001a002,...)。这样,上述竞争条件不会成为问题。

我们没有找到解决方法。提高应用程序的性能并增加Rack::Timeout的窗口,使这个问题几乎绝迹,但它仍然不时发生。

答案 1 :(得分:4)

这通常不是Postgres问题,而是像Unicorn这样的共享数据库连接的问题:

答案 2 :(得分:1)

这是我对Heroku的解决方案,不幸的是它有点参与其中。但是,从好的方面来说,当这个错误开始发生时,你不需要忍受100个错误通知。所需要的只是app / dyno重新启动。

该过程的基本概要是,当我们检测到ActiveRecord::StatementInvalid异常时,如果错误消息描述包含单词' prepared statement',我们运行heroku restart命令使用Heroku的platform-api宝石。

  1. platform-api gem放入Gemfile中,然后运行bundle install
  2. 将HEROKU_API_KEY设置为正确的值。 (您可以从Heroku仪表板生成密钥)。使用heroku config:set HEROKU_API_KEY=whatever-the-value-is
  3. 将HEROKU_APP_NAME设置为正确的值。您可以从heroku CLI获取此信息,但它只是您所谓的应用程序。
  4. 将以下内容添加到ApplicationController(/app/controllers/application_controller.rb)中:
  5. ...

    class ApplicationController < ActionController::Base
    
    rescue_from ActiveRecord::StatementInvalid do |exception|
      # notify your error handler, or send an email, or whatever
      # ...
      if exception.message =~ /prepared statement/
        restart_dyno
      end
    end
    
    def restart_dyno
     heroku = PlatformAPI.connect_oauth(ENV["HEROKU_API_KEY"])
     heroku.dyno.restart(ENV["HEROKU_APP_NAME"], "web")
    end
    
    end
    

    那就是它。希望这会有所帮助。