在不停机的情况下将大型Heroku Postgres实例迁移到AWS Aurora

2020-11-13 01:01:55

在本文中,我讨论了我最近使用的一个通用流程,该流程将一个大型的多TB Heroku Postgres数据库从Heroku平台迁移到基于Heroku的实时应用程序架构上的Amazon Aurora Postgres,在此过程中几乎没有宕机和内置故障转移。此迁移不仅节省了与运行大型托管Postgres实例相关的大量成本,还提高了参数图灵以及AWS RDS提供的其他管理功能的可扩展性和灵活性。

上面的Heroku Postgres迁移到AWS Aurora Postgres架构和流程流程图从较高层面展示了Heroku Postgres数据层架构对于具有高可用性的典型高级服务以及用于负载平衡读取繁重应用的读取副本的作用。然后右边是AWS平台边界内的迁移目标,标注了将数据迁移到最终Aurora Postgres实例所需的步骤序列。

在我开始讨论我所经历并发现值得注意的限制之前,我想提出一个重要的免责声明。Heroku平台是一项名义上的创新服务,通过降低进入门槛,为开发无数极其有用和有利可图的应用和服务铺平了道路。许多(如果不是大多数)应用程序不会达到我在下面指出的限制,这些限制需要将它们的数据层迁移出平台。

Heroku的成本可能很高,这或许是有充分理由的,因为它们省去了维护和扩展Postgres等服务所需的大部分sysop/devop投资和开发时间成本。

Heroku Postgres锁定了Postgres的大部分参数,这些参数通常是针对大型企业级、高吞吐量、Postgres使用情况进行调优所必需的。

Heroku Postgres监控远远不及许多其他选项,尤其是AWS RDS,这对大型企业级应用程序来说变得非常重要。

Heroku Postgres不允许Postgres超级用户或复制用户角色,因此迁移选项受到限制。

Postgres PG_DUMP/PG_RESTORE对于TB及以上的大型数据库来说不是一个可行的选项,因为它需要运行的时间很长,而且如果将其作为选项与ISN一起使用,则必然会导致停机或数据丢失,这对大多数应用程序来说都是不可能的

为了便于讨论,我提供了一个愚蠢的应用,可以部署到Heroku上,并使用Django web框架构建,它只需生成随机数字和从网络上抓取的报价。

它给出了以下输出,但请注意,重新输出将给出不同的应用程序名称和url。

使用以下命令在默认浏览器中打开该应用程序(将-a tenent-Headland-79519替换为您的应用程序名称)。

在现实世界中,要使此流程正常运行,您需要请求Heroku数据支持团队建立到AWS S3存储桶的连续Wal日志传送,以及使用基于Wal-E Python的库的基本物理备份。为了不麻烦Heroku的数据支持团队进行此演示,并允许读者完整地复制演示,我只需使用我自己的运行Postgres 11的AWS EC2实例来模拟这一步骤,并将一个连续的归档文件发送到我自己的AWS S3存储桶中。

Sudo yum更新-y Sudo Amazon-Linux-Extras Install Epel-y Sudo Amazon-linux-额外安装postgresql11-y Sudo yum安装PostgreSQL-SERVER PostgreSQL-contrib python3 lzop pv-y Curl https://bootstrap.pypa.io/get-pip.py-o get-pip.py Sudo python3 get-pin.py Sudo python3-m pip安装envdir wal-e[AWS]。

$isblk 名称:最小RM尺寸RO类型MOUNTPOINT XVDA 202:0 0 8G 0磁盘 └─xvda1 202:1 0 8G 0部件/ Xvdb 202:16 0 16G 0磁盘。

Sudo mkdir-p/etc/wale/env Sudo Chown-R EC2-User:EC2-User/ETC/Wal-e ECHO";INSERT-VALUE-HERE&34;>;/etc/wal-e/env/AWS_ACCESS_KEY_ID Echo";Region-此处>;/etc/wal-e/env/aws_Region ECHO";INSERT-VALUE-HERE&34;>;/etc/wal-e/env/AWS_SECRET_ACCESS_KEY Echo";S3-存储桶-文件夹-URL-here";>;/etc/wal-e/env/Wale_S3_Prefix Sudo Chown-R Postgres:postgres/etc/wal-e Sudo chmod-R 755/etc/wal-e/env。

LISTEN_ADDRESS=';*';或您的应用程序特定IP WAL_LEVEL=复本 ARCHIVE_MODE=ON ARCHIVE_COMMAND=';envdir/etc/wal-e/env wal-e wal-ush%p'; ARCHIVE_TIMEOUT=60

Sudo system ctl后台进程-重新加载 Sudo system ctl启动PostgreSQL SUDO系统控制状态PostgreSQL Sudo system ctl启用PostgreSQL。

替换DATABASE_URL配置变量以指向新启动的模仿Heroku Postgres的EC2 Postgres实例。

此时,您将希望注册一个新用户并生成更多要迁移的测试数据。您还可以使用PG_DUMP/PG_RESTORE将任何现有数据从Heroku Postgres传输到这个用于模拟Heroku Postgres的新EC2 Postgres实例。

作为个人偏好,我喜欢将可能长时间运行的命令包装在Shell脚本中,这样我就可以显式地回显事情开始和结束的时间,然后为了防止SSH连接在可能长时间运行的进程期间超时,我使用了带后台的nohup。

推送完成后,我应该能够使用以下命令验证备份是否已推送。

此时,我有一个安装了Postgres的EC2实例,它模仿Heroku Postgres并持续将备份和WAL传送到S3。

在现实世界中,您将从这里开始,即以物理基础备份和WAL文件的形式创建从S3中的数据加载的日志装运副本。Heroku数据支持团队可能会以Heroku配置变量的形式为您提供Wal-E的S3证书,您可以运行以下命令查看这些变量。

在本演示中,我将对和EC2 VPS使用以下规格。你需要根据你的需要和预算来调整。我的建议是使用EC2实例大小,最好比您当前的Heroku Postgres实例大。

Sudo yum更新-y Sudo Amazon-Linux-Extras Install Epel-y Sudo Amazon-linux-额外安装postgresql11-y Sudo yum安装PostgreSQL-服务器PostgreSQL-contrib python3 lzop-y Curl https://bootstrap.pypa.io/get-pip.py-o get-pip.py Sudo python3 get-pin.py Sudo python3-m pip安装envdir wal-e[AWS]。

$isblk 名称:最小RM尺寸RO类型MOUNTPOINT XVDA 202:0 0 8G 0磁盘 └─xvda1 202:1 0 8G 0部件/ Xvdb 202:16 0 16G 0磁盘。

使用Heroku提供的AWS S3凭证在目录中创建环境变量,wal-E和envdir库使用该环境变量来下载备份和wal文件。

Sudo mkdir-p/etc/wale/env Sudo Chown-R EC2-User:EC2-User/ETC/Wal-e ECHO";INSERT-VALUE-HERE&34;>;/etc/wal-e/env/AWS_ACCESS_KEY_ID Echo";Region-此处>;/etc/wal-e/env/aws_Region ECHO";INSERT-VALUE-HERE&34;>;/etc/wal-e/env/AWS_SECRET_ACCESS_KEY Echo";S3-存储桶-文件夹-URL-here";>;/etc/wal-e/env/Wale_S3_Prefix Sudo Chown-R Postgres:postgres/etc/wal-e Sudo chmod-R 755/etc/wal-e/env。

一旦创建了上述环境变量文件,通过使用以下命令列出可用的备份来测试WAL-E是很有帮助的。

我使用以下名为wal-e-fetch-backup.sh的bash脚本包装wal-E调用,以便从S3提取基本备份,因为它使我能够跟踪需要多长时间,并利用nohup防止SSH连接超时。

#!/bin/bash Echo";正在启动Wal-e Backup-Fetch"; 开始日期=`日期+%s` Envdir/etc/wal-e/env wal-e备份-获取--盲恢复/数据库最新 结束=`日期+%s` 持续时间=$((结束-开始)) Echo";Wal-e备份-回迁在$Duration秒";之后完成。

需要更新一些配置设置,以便将此副本用作到故障转移EC2 Postgres实例的流复制的源,以及用作到Aurora Postgres的逻辑复制的源。

LISTEN_ADDRESS=';*'; HOT_STANDBY=ON DATA_DIRECTORY=';/DATABASE'; WAL_LEVEL=逻辑 MAX_WAL_SENDERS=10 最大复制插槽数=10 WAL_KEEP_SECTIONS=1000 WAL_SENDER_TIMEOUT=60。

为了允许应用程序用户pguser连接到EC2 Postgres日志发送副本,以及postgres用户从流副本连接,我必须从EC2流副本的IP地址为pguser启用MD5密码身份验证,为postgres用户启用可信身份验证。来自故障转移流副本的postgres用户的受信任身份验证是必要的,因为Heroku没有可以连接的密码的复制用户,理想情况下,您希望流副本在升级日志传送副本后立即可用。

此外,我还为Aurora rds_Replication用户添加了另一个基于密码的身份验证条目,该条目将用于执行逻辑复制。

#";本地";仅适用于Unix域套接字连接 本地所有信任 #IPv4本地连接: 主机pgdb pguser 0.0.0.0/0 md5 托管所有rds_Replication 0.0.0.0/0 MD5 托管所有Postgres IP地址-此处/32信任 托管所有127.0.0.1/32信任 #IPv6本地连接: 托管所有::1/128信任 #允许从本地主机复制连接,由具有 #复制权限。 主机复制Postgres IP地址-此处/32信任 主机复制rds_Replication 0.0.0.0/0 MD5 本地复制所有信任 主机复制所有127.0.0.1/32信任 主机复制全部::1/128信任。

Recovery.conf文件指定应使用WAL-E程序从S3提取WAL文件,并将其持续恢复到最新的恢复时间表。

要管理PostgreSQL安装,我使用了一个位于/etc/systemd/system/postgresql.service的systemd服务文件和一个PGDATA环境变量,该变量指示要管理的数据库集群位于/database目录中。

Sudo system ctl后台进程-重新加载 Sudo system ctl启动PostgreSQL SUDO系统控制状态PostgreSQL Sudo system ctl启用PostgreSQL。

启动PostgreSQL服务后,通过查看PostgreSQL日志,您将能够看到WAL文件被拉入和恢复。

如前所述,我觉得让立即可用的WAL流副本作为热备份是一个好主意,以便在升级的日志传送副本无法确保应用程序在迁移期间保持可用时进行故障切换。当然,如果发生这种情况,需要使用Wal流副本作为源重新启动到Aurora的逻辑复制。

此EC2实例的基础架构应与EC2日志流副本使用的基础架构相同。

Sudo yum更新-y Sudo Amazon-Linux-Extras Install Epel-y Sudo Amazon-linux-额外安装postgresql11-y Sudo yum安装PostgreSQL-SERVER PostgreSQL-contrib python3 lzop pv-y Curl https://bootstrap.pypa.io/get-pip.py-o get-pip.py Sudo python3 get-pin.py Sudo python3-m pip安装envdir wal-e[AWS]。

$isblk 名称:最小RM尺寸RO类型MOUNTPOINT XVDA 202:0 0 8G 0磁盘 └─xvda1 202:1 0 8G 0部件/ Xvdb 202:16 0 16G 0磁盘。

为了复制这个流副本的数据库集群,我使用了Postgres 11安装附带的pg_base备份实用程序,方法是直接连接到日志流副本。与其他命令类似,我将这个对pg_base备份的调用包装在名为Physical_backup.sh的Shell脚本中,如下所示。

#!/bin/bash ECHO";开始物理备份"; 开始日期=`日期+%s` Pg_base备份-h IP地址-此处-D/数据库--进度--详细 结束=`日期+%s` 持续时间=$((结束-开始)) ECHO";物理备份在$DATION秒";之后完成。

然后使用nohup将其作为后台进程运行,以防止SSH连接超时,因为这可能会执行一段较长的时间。

只需初始化默认PostgreSQL目录,即可重复使用在上一步中从Log Shipping复制副本拉回的恢复的postgresql.conf文件,以拉入默认的Postgresql.conf文件。

同样,重要的是至少要确保应用程序用户(在此演示中为pguser)可以通过密码auth进行连接,以防您必须故障转移到此实例。

此Recovery.conf文件与Log Shipping复制副本中使用的文件不同,因为它告诉Postgres直接连接到Log Shipping复制副本以吸收WAL事务,而不是从共享存储设备(如S3或类似设备)读取它们。

就像Log Shift副本一样,我再次使用相同的systemd PostgreSQL服务文件来管理该服务。

Sudo system ctl后台进程-重新加载 Sudo system ctl启动PostgreSQL SUDO系统控制状态PostgreSQL Sudo system ctl启用PostgreSQL。

请记住,您可以检查PostgreSQL日志,以验证是否正在从Log Shipping副本中检索WAL。

好的,在旅途的这个阶段,EC2 Postgres Log发运的只读副本被升级,允许它开始接收写入和读取,Heroku应用程序的数据库切换到新升级的EC2 Postgres。这是从Heroku Postgres上获取Heroku应用程序所需的第一个或中间数据库切换。有必要将主数据库切换到EC2 Postgres实例,以便EC2 Postgres实例可以用于通过逻辑复制和随之而来的发布者/订阅者范例向Aurora Postgres进行逻辑复制,但是不能在只读(也称为热备份)副本上创建发布,因此必须将其提升为主数据库,并在此阶段从Heroku应用程序捕获到Aurora Postgres的新写入。

建立升级EC2 Postgres日志发运副本和切换Heroku的应用数据库的步骤。

2)升级EC2 Postgres日志发运副本*这在EC2 VPS上运行

3)通过环境变量翻转App上的Database URL将App重定向到新升级的数据库我使用了一个完全独立的环境变量,名为`AURORA_MASTER_DATABASE_URL`,在建立数据库连接的代码中,我只需检查该变量是否存在,并通过标准Heroku DATABASE_URL变量有选择地使用它进行连接。

例如,在本文的演示应用程序(也是Django)中,看起来是这样的。

设置Heroku环境变量的具体命令如下所示(同样是在本地运行)。您还会注意到,我在设置环境变量后显式重新启动Dyno,这是确保每个dyno重新启动并获得环境变量更新的好主意。

好的,目前Heroku应用程序不再使用Heroku Postgres数据库服务,完全基于AWS平台,但最终目标是重新使用托管数据库服务,即Aurora Postgres。当然,如果您的最终目标只是迁移到运行Postgres的EC2实例,那么您可以到此为止。

好的,此时需要设置逻辑复制以利用新升级的EC2 Postgres实例上的发布(也称为复制插槽)以及Aurora Postgres RDS服务上的订阅。

需要在现在充当Master的EC2 Postgres实例上创建名为rds_Replication的用户,该用户已存在于Aurora实例上,该实例将用于通过Aurora实例连接到该实例,以通过即将创建的发布/订阅复制插槽执行逻辑复制。

创建订阅后,Aurora将连接到EC2 Postgres实例,并开始逻辑复制。逻辑复制将分两个阶段进行:(I)初始表同步作为事务包装复制语句发生,以拉出表的当前数据,然后(Ii)发生改变数据捕获,由此在进行中的写入被传播到逻辑复制的订阅者。

如果要迁移大量数据,则需要增加Aurora群集参数组的max_sync_Worker_per_SUBSCRIPTION参数以及max_Worker_Processing和max_logic_Replication_Worker参数。有关详细信息,请参阅Postgres文档。

逻辑复制也不复制通常支持主键的序列。这可以通过在关闭逻辑复制之后、最终Heroku应用程序切换到Aurora之前立即更新每个序列的起始值来缓解。

对于非常大的表(10到100行数百万行),初始表同步步骤可能会失败一次或多次,但它会自动重新启动,根据我的经验,它们总是成功的。

好的,接下来进入最后阶段,在这一部分中,您将等待复制完全同步,复制延迟降至可接受的水平(理想情况下为零),然后终止订阅,更新序列,并将Heroku应用程序切换到Aurora Postgres。

Postgres提供了一个名为PG_STAT_REPLICATION的视图,该视图提供有关正在进行的复制状态的有用信息,其中一行专用于每个复制事件。在此演示示例中,表中有两个条目(I)一个用于流副本,(Ii)另一个用于向Aurroa的逻辑复制。该视图中最重要的行字段可能是WRITE_LAG、FLUSH_LAG和REPLAY_LAG,它们显示复制落后的时间量。这些字段的理想值为空,这意味着没有延迟。

因此,在Master EC2 Postgres服务器上,您可以查询该表来评估进度。

您还应该在CloudWatch中检查来自Aurora的PostgreSQL日志,并确保所有表都完成了初始同步阶段。

正如我前面提到的,逻辑复制不会正确迁移序列,因此如果尝试将新记录插入Aurora上的某个表中,则会发生冲突错误,因为即使表数据已经存在,所有主键值都将从1开始。我通过使用SQL函数查询每个主键的表(利用序列)来找到最大主键值,并重新启动序列值,使其达到最大值加上一定的偏移量,从而缓解了这个问题。

对于这个演示应用程序,我有一个名为Alter_Sequences.sql的SQL脚本,它可以实现这一点。

CREATE OR REPLACE函数ALTER_SEQUENCE(seq text,SELECT_SQL text)返回void 语言plpgsql 并行不安全 作为$$ 申报 MAX_PK整数; New_seq_id整数; SEQ_ID_OFFSET整数:=9000; 开始 执行SELECT_SQL INTO max_pk; New_seq_id=max_pk+seq_id_offset; 从SQL=%';,max_pk,new_seq_id,select_sql;提出通知';最大主键=%,新序号=%,新序号=% 执行setval(seq,new_seq_id); 结束;$$; 开始; SELECT alter_sequence(';public.auth_group_id_seq';,';SELECT MAX(Id)from Public。auth_group';); SELECT alter_sequence(';public.auth_group_permissions_id_seq';,';SELECT MAX(Id)from Public。auth_group_permises';); 选择alter_sequence(';public.auth_permission_id_seq';,';从Public中选择Max(Id)。auth_permission';); SELECT alter_sequence(';public.auth_user_groups_id_seq';,';SELECT MAX(Id)from Public。auth_User_Groups';); SELECT alter_sequence(';public.auth_user_id_seq';,';SELECT MAX(Id)from Public。auth_user';); 选择ALTER_SEQUENCE(';Public.auth_user_user_permissions_id_seq';,';从发布中选择MAX(Id)。auth_user_user_permises';); SELECT alter_sequence(';public.core_randonumba_id_seq';,';从公共核心选择MAX(Id)。core_randonumba';); 选择alter_sequence(';public.django_admin_log_id_seq';,';从Public中选择Max(Id)。django_admin_log';); 从alter_sequence(';public.django_content_type_id_seq';,中选择最大(Id)。django_Content_。

.