不可能创建跨主要版本的零停机的Postgres升级--对吗?如果我错了,请纠正我 :)
但至少我们已经找到了一个接近零停机时间的方法
在 Listen Notes,自2017年以来,我们已经进行了两次Postgres主要版本的升级,Listen Notes成立的那一年。在这些升级过程中,我们经历了 "读 "操作的零停机时间,以及 "写 "操作的不到1分钟停机时间。
让我们走过我们在Listen Notes升级Postgres的过程。
TL;DR
- 用旧版本的Postgres配置一个新的复制数据库(DB_A)。
- 在在线服务器的"/etc/hosts "中改变DB主机的IP地址,以使用只读复制的DB(不是DB_A)。这时,所有的写操作都会失败。
- 在DB_A上运行 pg_upgrade(带"--link")来升级到新版本的Postgres,并将DB_A升级为主服务器。
- 在在线服务器的"/etc/hosts "中替换所有DB主机的IP地址,以使用DB_A。到这时,写操作将恢复。
- 用新版本的Postgres重新配置新的复制节点。
我们如何在Listen Notes中使用Postgres
Listen Notes是一个流行的播客搜索引擎。我们提供一个每月有数百万页浏览量的网站(ListenNotes.com),以及一个由成千上万的第三方应用程序和网站使用的坚实的播客API。
我们使用Postgres作为我们的主要数据库,它存储所有的播客、剧集元数据和用户数据。
我们在AWS EC2上运行一个自我托管的Postgres集群,包括一个用于写和读操作的主数据库(db1),以及两个用于只读操作的副本(db2和db3)。数据库的规模比1TB小一点。
Postgres的进展
当Listen Notes在2017年开始时,我们运行Postgres 9.6。
一般来说,我们不愿意使用任何基础设施软件的最新版本,无论是Postgres、Django、Redis还是其他软件。
我们相信Postgres的质量,但最新版本的文档和在线讨论可能较少,当发生特定版本的问题时,这可能会使故障排除变得困难。
与Postgres DB对话的Listen Notes服务器使用的是像db1.internal.ln、db2.internal.ln、db3.internal.ln这样的主机名。
这些主机名都在"/etc/hosts "中,所以在保持主机名不变的情况下,很容易改变实际的IP地址。
我们的Postgres集群有在线和离线的工作负载。在线工作负载是为我们的网站(ListenNotes.com)和API端点(PodcastAPI.com)提供服务,不能有长时间的停机时间(例如,超过5分钟)。
离线工作负载运行Celery任务和其他脚本,可以停止相对较长的时间(例如,2小时)。你可以阅读我们过去的博文,了解Listen Notes架构的细节。
对于跨主要版本的Postgres升级,我们的目标是实现读操作的零停机时间和写操作的最小停机时间(小于5分钟)。这将确保我们的大多数用户在升级的时候不会受到影响。
如何为 Postgres升级做准备
实际的升级可能只需要30分钟,但我们通常会花几个工作日来准备,这样可以增加升级时的成功率。
准备步骤1:
我们必须确保新的主要版本的Postgres能与我们的代码库很好的配合。因此,我们在开发和暂存中测试新版本的Postgres。
除了自动单元测试外,我们还必须手动测试所有主要的产品功能。
准备步骤2
在线服务(ListenNotes.com和PodcastAPI.com)即使在Postgres写操作被禁用的情况下,对大多数用户来说也应该是正常的。
对于ListenNotes.com,大多数用户正在进行 "只读 "任务,如搜索播客、浏览播客细节和类似的无害操作。这意味着 "写 "的失败应该只影响极小部分的用户。
对于PodcastAPI.com来说,所有的API端点都是只读的,或者将写操作卸载到异步离线任务中,所以写操作可以被暂时禁用。
我们会花一些时间在staging上测试,以确保在线服务在数据库写操作被禁用时仍能正常运行。
准备步骤3
我们花最多的时间来演练升级Postgres的过程。基本上,我们提供了整个Listen Notes车队,并练习了升级Postgres的所有必要步骤。
我们尝试将一些步骤编入Ansible或Bash脚本,以实现自动化。我们对每个步骤进行记录和计时。当我们在生产环境中执行时,我们知道每个步骤将花费多少分钟(甚至几秒钟)。
准备步骤4
我们练习如何快速回滚到旧版本的Postgres,以备升级失败,我们被迫尽快恢复一个稳定的环境。
如何升级 Postgres
我们通常在周五晚上进行实际的升级,这时网站和API流量较低。另外,我们必须在白天好好休息一下,以保留足够的精力在当天晚上的生产中进行这种危险而紧张的操作 :)
由于在前几天的准备工作中,我们在Notion中创建了一个详细的TODO列表,所以我们仔细按照TODO列表来升级Postgres。
升级步骤1:
我们用旧版本的Postgres配置一个新的只读复制的DB。让我们把它叫做DB_A。它将实时同步来自主数据库的数据,并将首先升级到新版本的Postgres,然后被提升为主数据库。
如果DB_A的升级失败,我们仍然可以选择快速回滚并使用旧的主数据库。
升级步骤2:
我们停止所有的离线任务,除了一个处理一些时间敏感的异步任务的Celery工作者,例如发送登录邮件。我们将在步骤4之前停止这个Celery工作者。
我们还将大多数Web/API服务器从负载均衡器中移除,只留下最小的在线服务器。
升级步骤3:
我们在最小的在线服务器群(例如,Web、API......)上改变"/etc/hosts "中所有DB主机的IP地址,以使用一个旧的只读DB。我们把它叫做DB_B。
从这一点开始,所有的写操作都应该失败。这一步是为了确保未来新的主数据库不会有过时的数据。
升级步骤4:
我们在DB_A上运行pg_upgrade(带"--link")来升级到新版本的Postgres,并将其提升为主数据库。从现在开始,DB_A是主数据库,运行新版本的Postgres。
升级步骤5:
我们用DB_A的IP替换所有在线服务器(例如,Web,API...)的"/etc/hosts "中出现的DB_B的IP。至此,DB_A被用作主服务器和副本。而且,写操作现在应该是好的。
升级步骤6:
我们改变"/etc/hosts",将DB_A用于所有其他服务器上的DB主机(主服务器+副本),并将离线任务带回来。
从用户的角度来看,所有的Listen Notes服务现在应该是正常的。事实上,在整个升级过程中,所有API用户应该不会遇到任何中断,而极少数网站用户在执行 "写操作 "时可能会出现错误,例如创建播客播放列表或剪辑。
升级到新版Postgres的最重要步骤
其中,步骤4是最关键的。如果它失败或运行时间过长(例如,超过10分钟),那么我们必须通过改变这些在线服务器上的"/etc/hosts "来回滚。
根据我们的经验,运行步骤4需要不到1分钟。如果你有一个更大(或更小)的数据库,你的里程可能有所不同。
在我们确认步骤6之后事情恢复正常之后,我们可以用新版本的Postgres重新配置复制的DB实例。而最终,我们终止了旧的DB实例。
常见问题
为什么不使用管理型Postgres,例如Amazon RDS?
我们想完全控制关键的基础设施软件(例如,Postgres,Elasticsearch...),因为...
- 我们不希望被平台锁定
- 我们希望了解服务器内部的情况,避免无助地等待第三方客户支持团队(例如AWS)来帮助解决黑盒子内部的紧急生产问题
- 对我们来说,自己运行Postgres实例更划算--如果钱不是问题(例如,筹集大笔VC资金),或者我们的Postgres操作经验较少,那么Amazon RDS可能是一个不错的选择,可以先用。就像生活中的许多事情一样,我们需要在约束条件下做事情(比如金钱、时间、专业知识......)。
据我所知,使用管理型Postgres(如Amazon RDS)不会消除跨主要版本升级的痛苦。Google "Amazon RDS升级Postgres版本,零停机时间"。
你为什么不使用第三方工具将这一过程自动化一些(比如一键推送,将整个过程自动化)?
我们不知道是否有任何可靠的第三方工具,易于使用,易于理解,使用安全......但我们愿意接受推荐--wenbin@listennotes.com。
我们经常需要评估是否值得花时间和风险去学习并在生产中使用新的黑盒工具,特别是对于严肃的DevOps任务。
你为什么不使用MySQL、MongoDB或其他非Postgres数据库?
当我开始做Listen Notes时,我对Postgres的了解远远超过MySQL和其他数据库,因为我以前的雇主Nextdoor.com使用Postgres。而且我知道Instagram和其他大规模的在线服务也使用Postgres作为他们的主要数据存储(至少在最初几年)。
如果Postgres对巨大的在线服务很有效,那么它也应该对Listen Notes有效 :)有时我们花时间学习新的技术来启动一个项目,但更多的时候我们只是使用我们已经知道的技术,以便更快更有效地启动一个项目。
同样,跨主要版本的非Postgres数据库的升级也是不容易的......但希望以上所有的步骤能帮助你进行的Postgres升级获得成功