只读模式可缩短Rails停机时间

2020-10-15 01:56:48

最近,我希望在我一直在开发的一个应用程序上升级Postgres版本。这将需要少量的停机时间,可能约为10分钟。

在这些情况下,我想要的默认解决方案是进入Heroku的维护模式,该模式提供一个带有503ServiceUnailable状态代码的HTML维护页面。这是可行的,但在升级过程中会使应用程序完全不可用,我希望能找到更好的解决方案。在本例中,我还希望能够提供JSON响应,因为应用程序主要为移动应用程序提供API。

在研究了几个不成熟的选项之后,我决定使用仅限区域的数据库连接,以仍然允许读取,但防止任何写入发生。在使用只读连接时,Postgres适配器会在我们试图更改数据库中的数据时引发错误,但我们可以轻松地修复此特定错误并将其转换为面向用户的通知。我觉得使用异常作为这个工作流程的核心有点奇怪,但最终,它工作得非常好,所以我想分享一下具体细节。

值得一提的是,这个解决方案特别适合这个特定的应用程序,它只提供一个API,使用起来非常繁重,但我想它也可以扩展到与其他风格的应用程序一起使用。

如果存在,Rails将使用DATABASE_URL环境变量中的连接字符串连接到数据库。按照Rails指南中的连接首选项说明,我意识到可以明确使用DATABASE_URL并允许临时覆盖。为此,我为生产环境添加了一个具有所需连接首选项的显式urlproperty:

准备就绪后,我只需设置DATABASE_URL_READ_ONLY环境变量即可启用只读模式:

注意:我能够使用Heroku的Postgres凭证界面创建只读用户,但是如果您不使用Heroku,您应该能够使用这些说明来创建您的只读用户。

使用我考虑的其他方法,我发现我必须关闭向数据库发出写入的多个不同的潜在方式,但是只读连接可以很好地在一次更改中切断所有内容。这就是说,这只是解决方案的一半,因为我当然不想让错误让它变得更糟。

谢天谢地,提供集中式救援相对简单,这样我就可以处理所有错误。首先,我使用Rails的ActiveSupport::Concern功能创建了一个模块:

#app/controllers/concerns/read_only_controller_support.rb模块只读控制器支持扩展ActiveSupport::Concerns Included Do IF ENV[";DATABASE_URLREAD_ONLY";]。现在时?。REASURE_FROM ActiveRecord::StatementInvalid DO|ERROR|IF ERROR。留言。是否匹配?(/pg::不充分权限/i)Render(status::service_unavailable,json:{info:";应用当前处于只读维护模式。请稍后重试。";,},)否则引发错误END END END。

当包含该模块时,该模块将使用Rails的REASE_FROM方法来捕获可能相关的错误,然后我们在该块内进行快速检查,以确保我们只捕获相关的错误。

注意,RESAVE_FROM逻辑仅在设置了DATABASE_URL_READ_ONLY时才启用,因此我们可以重用该变量的存在作为确定此行为范围的一种方式。

我最初使用这种只读模式的用例只需要支持API请求,但我可以想象将其扩展到HTML和基于表单的界面。

我要考虑的第一件事是添加一个站点范围的横幅,声明我们处于只读维护模式,以提醒用户当前状态。

有了这些,我认为我们可以扩展ReadOnlyControllerSupport模块中的错误处理,以将用户重定向回来并显示异常消息:

REASURE_FROM ActiveRecord::StatementInvalid DO|ERROR|IF ERROR。留言。是否匹配?(/PG::不充分权限/I)RESPONSE_TO|FORMAT|FORMAT。JSON DO#JSON错误消息,如上所示结束格式。HTML DO REDIRECT_BACK(FLABACK_LOCATION:ROOT_PATH,ALERT:";应用程序当前处于只读维护模式。请稍后重试。";,)END END否则引发错误END END

这里的另一个注意事项是关于后台作业和调度程序进程。对于后台工作,事情相对简单-我们只需要在只读期间将我们的工作人员池缩减到零即可。

调度器进程有点棘手,因为我没有全局启用或禁用它们的机制。考虑到这一点,我认为理想的解决方案是只让调度程序进程将作业排入队列,而不实际执行任何超出该队列的工作。

我们遇到的最后一个症结是移民。我们在Procfile中定义了一个RELEASE命令,该命令被配置为运行rake db:Migrate。不幸的是,即使没有运行迁移,Rails仍会尝试写入ar_Internal_METADATA表,作为db:Migratecomand的一部分,并且Heroku将在我们更改环境的任何时候运行Release命令。在我最初的尝试中,当我尝试设置DATABASE_URL_READ_ONLY时,Heroku失败了,因为在运行rake db:Migrate时,相关的Release命令遇到了只读错误。

为了解决这个问题,我编写了一个小脚本,它首先检查是否有任何迁移需要运行,并且只有在有的情况下才会运行,然后运行rakedb:Migrate:

此脚本作为bin/Migrate-if-Need添加到repo,然后将我们的调用放到rake db:Migrate with bin/Migrate-if-Need