我正在使用Rails 5.x应用程序,并且我使用Postgres作为数据库。
我经常在生产服务器上运行rake db:migrate
。有时,迁移会将新列添加到数据库,这会导致某些控制器操作因以下错误而崩溃:
ActiveRecord::PreparedStatementCacheExpired: ERROR: cached plan must not change result type
这是在需要零停机时间的关键控制器操作中发生的,所以我需要找到一种方法来防止这种崩溃的发生。
我是否应该捕获ActiveRecord::PreparedStatementCacheExpired
错误并重试save
?还是应该向此特定的控制器操作添加一些锁定,以便在数据库迁移运行时不开始处理任何新请求?
防止此崩溃再次发生的最佳方法是什么?
通过使用此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
我会这样使用它:
MyModel.retry_on_expired_cache do
@my_model.save
end
[不幸的是,这就像玩“打hack鼠”,因为在我的滚动部署期间,崩溃一直持续在我的整个应用程序中发生(我无法同时重新启动所有的Rails进程。]
我终于知道我可以关闭prepare_statements来完全避免此问题。 (请参见this other question and answers on 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)
这使我可以继续从DATABASE_URL
env变量设置数据库配置,并且'prepared_statements' => false
将被注入到配置中。
这完全解决了ActiveRecord::PreparedStatementCacheExpired
错误,使我的服务更容易实现高可用性,同时仍然能够修改数据库。