如何在运行 `rake db:migrate` 后立即防止任何 ActiveRecord::PreparedStatementCacheExpired 错误?

ndb*_*ent 5 database postgresql transactions ruby-on-rails database-migration

我正在开发 Rails 5.x 应用程序,并使用 Postgres 作为数据库。

rake db:migrate我经常在生产服务器上运行。有时,迁移会向数据库添加新列,这会导致某些控制器操作崩溃并出现以下错误:

ActiveRecord::PreparedStatementCacheExpired: ERROR: cached plan must not change result type
Run Code Online (Sandbox Code Playgroud)

这种情况发生在需要零停机时间的关键控制器操作中,因此我需要找到一种方法来防止这种崩溃发生。

我应该捕获ActiveRecord::PreparedStatementCacheExpired错误并重试吗save?或者我应该向这个特定的控制器操作添加一些锁定,以便在数据库迁移运行时不会开始服务任何新请求?

防止此类事故再次发生的最佳方法是什么?

ndb*_*ent 3

我可以使用这个retry_on_expired_cache助手在某些地方解决这个问题:

class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true

  class << self
    # Retry automatically on ActiveRecord::PreparedStatementCacheExpired.
    # (Do not use this for transactions with side-effects unless it is acceptable
    # for these side-effects to occasionally happen twice.)
    def retry_on_expired_cache(*_args)
      retried ||= false
      yield
    rescue ActiveRecord::PreparedStatementCacheExpired
      raise if retried

      retried = true
      retry
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

我会这样使用它:

  MyModel.retry_on_expired_cache do
    @my_model.save
  end
Run Code Online (Sandbox Code Playgroud)

不幸的是,这就像玩“打地鼠”游戏,因为在滚动部署期间,这种崩溃在我的应用程序中不断发生(我无法同时重新启动所有 Rails 进程。)

我终于了解到我可以关闭prepared_statements来完全避免这个问题。(请参阅StackOverflow 上的其他问题和答案。)

我担心性能损失,但我发现很多设置过的人的报告prepared_statements: false,他们没有注意到任何问题。例如https://news.ycombinator.com/item?id=7264171

我在以下位置创建了一个文件config/initializers/disable_prepared_statements.rb:

db_configuration = ActiveRecord::Base.configurations[Rails.env]
db_configuration.merge!('prepared_statements' => false)
ActiveRecord::Base.establish_connection(db_configuration)
Run Code Online (Sandbox Code Playgroud)

这允许我继续从DATABASE_URL环境变量设置数据库配置,并将'prepared_statements' => false被注入到配置中。

这完全解决了ActiveRecord::PreparedStatementCacheExpired错误,并使我的服务更容易实现高可用性,同时仍然能够修改数据库。