工程

氧化还原和Amazon RDS:非事件的艺术

发表于八月20,2017
Dharma Indurthy着

在Redox,我们使用AWS满足我们所有的基础架构需求,登录AWS控制台通常使我想起小时候走进玩具店的乐趣–有很多很酷的事情可玩。不幸的是,并非所有服务都对Redox有意义。我不清楚,例如我们如何利用 亚历克斯,尽管Redox有很多有远见的人,所以我不能保证我们 惯于 use it 上 e day.

另一个限制因素是符合HIPAA。 并非所有Amazon服务都符合要求,因此某些玩具无法满足Redox的核心需求。例如,我们希望将AWS Lambda(亚马逊的无服务器架构解决方案)集成到我们的更多核心服务中,但是由于合规性原因,我们不能*。幸运的是,亚马逊一直在努力扩大符合HIPAA要求的服务质量,因此情况总是在变化,但与此同时,一些玩具也令人大跌眼镜。 (*已更新-自8/31起,AWS Lambda  HIPAA Compliant).

2016年11月29日,在亚马逊年度AWS会议ReInvent上, 亚马逊宣布Postgresql RDS符合HIPAA。 RDS是Amazon的托管数据库服务,在那之前,它对我们来说是遥不可及的(我们将postgres用作存储持久数据的存储)。 ReInvent上的这一公告对我们来说是最令人激动的发现之一,我们很快开始计划迁移。

在我参与的项目中,这是一个反复出现的主题,前进的道路需要巨大的变化,希望没人能注意到。在Redox,至少就基础架构而言,我们的目标是为我们的客户创造大型活动,而不为客户创造大型活动,而这一目标在这里得以实现:将我们的核心数据库迁移到全新的系统中,并且做到这一点有人会注意到。


为什么选择RDS?

自我管理数据库会带来很多挑战,尤其是如果您打算构建高可用性的服务时。当然,在架构和部署可靠的数据库系统时会产生前期成本。我们已经在这项工作中取得了成功-我们正在使用已经满足HIPAA要求的主服务器和从服务器的高可用性配置。我们已经建立了用于自动故障转移的业务流程。毕竟,人们可能会问,为什么要迁移?

好吧,任何自我管理的服务都需要维护。有一系列的任务,例如操作系统修补,次要版本升级,安全监控等,这些工作需要花费时间和精力。我们还使用了一种配置,其中故障转移数据库也用作只读副本。由于此设备履行多项职责,因此某些故障恢复服务可能会受到故障转移/副本的影响。当然,我们可以做得更好,但是部署第三个副本实例并划分角色是一个项目。 RDS可以立即解决此问题.

我们还支持Amazon的故障转移解决方案。安排可靠而有效的故障转移非常具有挑战性,弄错它可能会导致服务使用者的中断时间延长,或者出现数据损坏或大脑裂等更糟的情况。在设计我们自己的自我管理故障转移时,我们发现要成功地快速恢复并保持高保真度是非常困难的,而且我们经常以牺牲前者的代价来追求后者。

总而言之,管理我们项目列表中的数据库集群占用的空间会浪费我们的时间,资源和自由度,从而需要在核心服务上进行更多投资。

登船

我们从研究开始。 AWS优惠 综合文件 关于RDS的工作方式和架构方法。但是,他们的 有关如何迁移的文档 想要。天真的,在我读很多书之前,我想我们可以使用postgres本机复制来构建RDS集群,然后在 下一个计划的维护时段,升级集群并将应用程序指向它。我们已经对自我管理的数据库进行了几次这样的数据库迁移。但是,AWS文档中显然没有这种方法。很快变得很明显,postgresql RDS实例不能用作自我管理数据库的副本,也不能创建RDS实例的自我管理副本。这是因为  安全 原因 特别是对于postgres 这些选项可用于MySQL.

幸运的是,AWS提供了其数据迁移服务(DMS),该服务支持从 postgres来源 to a Postgres目标。阅读了 先决条件 and 局限性,我决定DMS可以为我们工作,除了一个大问题:我们使用的是Postgresql 9.3,而DMS支持的最低版本为9.4。因此,这意味着我们需要首先升级数据库。由于我们计划的维护限制,这有效地延长了我们的项目从四分之一项目迁移到四分之一项目的时间。

此外,我们发现很少有资源详细描述了我们正在尝试的操作。 一些杠杆的外部工具,例如 布卡多 执行更改。有些是 详细但失败的努力。显然,这并没有我想的那么容易。


升级Postgresql

升级postgres会带来一系列挑战。跨不同主要版本的Postgresql复制 不可能,因此我们有效地执行了两项重要的数据库迁移:1)升级到Postgres自我管理的集群,以及2)到Postgres RDS。严格来说,我们可以执行升级而不是迁移。但是,当时,氧化还原数据库已增长到数百GB,我们受到最大计划停机2小时的约束。升级到另一个主要版本会在postgres数据文件系统上运行转换,在我们的情况下,该转换花费了几个小时,因此我们制定了迁移计划。

那么,当没有本地复制可依靠时,如何执行数据库迁移呢? 很明显,我们需要对升级后的postgres实例/集群执行某种基础副本,然后将新的和更新的数据复制到副本中,以便在切换之前将其更新。 幸运的是,postgres提供了一些工具来执行 数据库转储 and a 恢复 which 能够 跨版本工作。您甚至可以通过将前者的输出传递给后者而无需中间平面文件来执行此操作。经过一些试验,我们决定使用此方法:

 

pg_dump-h [SOURCE IP/DNS] -U [SOURCE DB USER] -Fc [SOURCE DB NAME] | 
sudo -u [TARGET DB USER] pg_restore -v -h [TARGET DB IP/DNS] --dbname=
[TARGET DB NAME]

 

We found it necessary to use the custom pg_dumpformat, i.e. the -Fc argument, to successfully copy across major releases.

因此,我们有一个计划:执行 pg_dump – pg_restore 要生成一个升级的数据库,然后在停机期间,请执行以下操作:1)执行增量副本以使其保持最新状态,以及2)重新指向该应用程序。我们知道增量副本将是我们最脆弱的部分,因为它是我们自己构建的东西。

我们选择了9.5的目标版本,这几乎是当时的最新版本。特别是,此版本启用了INSERT的ON CONFLICT子句,该子句实际上起UPSERT动作的作用。事实证明这至关重要。

我们的postgres数据库由一组大多数静态数据(如组织设置,源和目标设置)以及动态数据(如消息和传输)组成。 前者占我们表的大部分,但后者占磁盘上数据的大部分。关系数据库中的数据很可能也符合这些常规类别。对于我们的大多数静态和动态数据,我们都有可靠的时间戳字段,这些字段指示何时创建或最后更新相应的记录。对于我们的大多数表,尤其是最大的表,索引一个或两个字段。这使我们能够以编程方式复制增量更改。

我们知道执行增量复制可能会导致复制新行,并且 更新的行。确定行是新行还是更新行是不平凡的问题。 UPSERT功能拯救了我们。我们可以对bash中的给定表执行以下操作:

upstatement="上  conflict ($PRIMARYKEY) do update set "

for colind in "${!COLUMNS[@]}"; do

column="${COLUMNS[colind]}"

upstatement="$upstatement\"$column\"=excluded.\"$column\""

if [[ "$(($colind + 1))" -lt "${#COLUMNS[@]}" ]]; then

upstatement="$upstatement, "

fi

done

echo "insert into public.\"$table\" (select * from \"temp$table\") 
$upstatement" | tee /dev/tty | sudo -u postgres psql $TARGETCONNECTION

这实际上意味着,如果我们在INSERT期间遇到主键冲突(即,我们正在插入已经存在的主键),我们将应用该声明,该声明将把插入记录中的值复制到我们碰撞的记录中用。三通允许UPSERT记录到STDIO以及通往目标的管道。


测验

这不是一个概念上复杂的操作,但是数据库本质上是复杂的生物,并且总是存在陷阱。另外,我们需要确保有时间安排,以便我们可以在分配的中断时间内完成操作。幸运的是,我们在基础架构组成以及入站和出站流量方面都具有类似于生产的测试环境。我们使用一种测试线束,通过一个可用于放大或缩小的拨号盘模拟我们的客户流量(将在即将发布的博客文章中详细介绍)。非产品数据库本身由与生产类似的数据量组成,因此我们需要进行一些实际操作。我将整个努力的成功归功于我们测试环境的忠诚。

陷阱

我们发现的一个陷阱是,复制记录不会更新串行主键的下一个ID。因此,一旦切换到新消息或在升级后的数据库上创建传输,它将分配一个与现有消息冲突的ID。因此,我们必须在bash中使用类似以下的命令来使用串行主键更新表:

if [[ "$PRIMARYKEYTYPE" == "integer" ]]; then

echo "SELECT setval(pg_get_serial_sequence('$TABLE', '$PRIMARYKEY'), 
coalesce(max($PRIMARYKEY'),0) + 1, false) FROM public.\"$TABLE\"" | 
tee /dev/tty | sudo -u postgres psql $TARGETCONNECTION

fi

上面的命令适用于目标数据库公共架构上存在的给定$ TABLE。 对于每个具有整数ID的表,我们将主键的序列号设置为表中的最大ID加1。这里的一个假设是,当且仅当它是整数时,我们数据库中的主键才是串行键。类型。在测试时,我们的数据库也是如此。

另一个难题是,并非每个表都对更新后的字段建立了索引,因此对于我们的大表而言,增量副本的时间过长。幸运的是,我们可以依靠创建的字段,但这增加了副本遇到冲突的可能性。因为我们利用了UPSERT,所以这不是真正的问题。一旦我们使用了正确的字段,复制将在20分钟内成功完成!

最后,在测试过程中,当应用程序数据库迁移更改了主数据库上的数据结构时(例如在现有表中添加一列),我们遇到了错误。数据库是一个移动的目标,不仅是在我们拥有多少内容方面,还是在结构上。当然会增加生产活动,而开发人员通常会添加列或表。考虑了冻结对数据库结构的更改,但是冻结将延迟其他项目,并且使冻结既狭窄又有效是一项挑战。相反,我们使用一种策略来同步当前集群和升级集群之间的迁移 之前 执行任何增量副本。这在我们的测试中取得了成功,并且使我们有信心在不限制发展的情况下进行机动。

由于这是一项非常基础的更改,因此我们进行了形式化测试,并进行了一系列回归测试,以确保升级操作后发动机能够正确运行。我们还需要验证9.5数据库性能,我们确定它等于或优于9.3。

我们已于2017年4月8日成功执行了该操作,几乎没有出现任何问题。最后一个难题是,与该项目并行,应用程序更改创建了新表,这些表的字段使用bigint数据类型作为串行主键。我在上面提到了一个假设,即序列ID只能是整数,因此我们没有更新这些字段的下一个ID。冻结本可以避免此问题。我们仅在迁移到生产后才发现此问题,但我们很快应用了数据库迁移来执行id更新。由于反应迅速,因此发动机的生产运行不受影响。


迁移到RDS

鉴于我们要执行的是非常规升级,可能会问我们是否应该使用相同的方法直接迁移到RDS。我们本来可以创建一个9.5 RDS集群,并对其应用相同的pg_dump / pg_restore +自定义增量副本。我们之所以选择不这样做,主要是因为我们想花一些时间尝试RDS。 RDS故障转移机制对我们来说是新的,就像使用它一样 数据库恢复工具。我们想花一些时间来了解RDS,然后再过渡到RDS。而且,鉴于 DMS是亚马逊推荐的迁移工具,我们默认使用该指导。

在上述研究阶段,我们决定DMS可以为我们工作。当时,尚无cloudformation支持,因此,这纯粹是手动操作(现在已经改变了)。我们根据文档设置DMS,并使用我们的测试环境主数据库创建了副本。我们立即发现了一个问题:我们的JSON字段被截断了。显然,这对我们没有好处。我们联系了Amazon支持以了解发生了什么,并收到了以下回复:

感谢您对此情况的详细说明,您对JSON的理解是正确的,因为通常支持数据类型,而不支持JSONB。不幸的是,任何大于512的数据都会在迁移过程中被截断。它’s是一个已知的错误,我已经在与DMS服务团队合作以找到解决方法并解决该问题,并且服务团队正在努力获得具有较高优先级的解决方法。不幸的是,我不’在何时可以解决该问题的时间,没有ETA。

亚马逊以从来没有提供时间表而臭名昭著,如果您经历过不同的事情,这对您来说是一个荣誉。对于此问题,我们没有获得可接受的解决方法。

幸运的是,我们先前向升级的Postgres集群迁移的过程为我们提供了完成RDS机动的工具。我们最终使用了相同的方法来执行转储/还原和增量复制到转换。进行此操作的时间与我们进行postgres升级的结果非常相似,这并不奇怪,因为我们将RDS集群的大小与自管理数据库集群的大小相同。

因此,我们大部分时间都花在了适应RDS上。我们执行了故障转移 执行重启,效果很好。应用程序故障转移几乎是无缝的,没有证据表明数据丢失会导致大脑分裂。在30分钟内完成了生产规模的数据库的还原,这是对我们自管理数据库功能的巨大改进。我们还在非生产状态下运行了几周,以查看是否存在任何不稳定因素或 维护窗口 对我们的运营造成了明显的干扰。在这段时间内,我们没有发现任何中断。

RDS确实对Redox的运行方式进行了一些更改-我们丢失了一些我们依赖的指标(例如平均负载),因此我们调整了监控以使用CPU。 我们已经将每日数据库备份和日志存储在S3中,但是RDS仅允许30天的备份和几周的日志。为了维持归档级别(出于合规性原因而被认为是必需的),我们创建了一个备份管理器,这是一个自我管理的实例,它将每天从RDS集群副本中获取基本备份,并将其保存到S3,以及将任何新的postgres日志复制到S3。这样,我们在过渡中没有丢失任何东西,只是对时间超过30天的数据库映像进行了时间点还原。

我们于2017年7月8日成功切换到RDS。除了在停电期间进行的其他维护外,我们还提前15分钟完成了工作,没有明显的问题。在接下来的几天里,我们在解决RDS终结点时确实遇到了一些短暂的DNS解析失败。我们依靠Amazon DNS,因此原因对我们来说是不透明的。这导致了一些数据库断开连接,但是应用程序恢复了,没有中断。

 

外卖

我们成功完成该项目的原因有三个:

考虑到数据库的基本性质,像这样的迁移项目应该从焦虑开始,但要自信地结束。通过测试和研究,我们只能获得那种信心。到目前为止,RDS已经为我们提供了出色的性能,并且没有中断Redox服务。

 

我很自豪地说,到目前为止,没有人能说出其中的区别。