Postgres模式更改仍然是PITA
#postgres #database #开发人员 #postgressql

我们的软件工程师对此不同意,但我们同意这一点:数据库模式的变化是A **的痛苦。

我在XATA工作的一部分是与尽可能多的开发人员进行交谈 - 从新鲜的训练营毕业生到独立开发人员到大型团队工作的主要工程师。我们一般谈论数据库,他们面临的问题,使用的工具等等。

与我们与之交谈的人们,几乎每个人都说模式变化和模式管理是使用数据库的最不喜欢的部分之一。尽管这种情绪非常普遍,但他们提出的原因并不总是一样。例如,从事业余项目的小型公司或开发人员必须随着应用程序的增长而进行大量更改,并发现了新的要求。他们的模式会更改工作流程,就像在github上的拉请求一样简单明了。

Simpsons he's miserable

对于具有更多数据和高流量表的大型公司而言,模式变化的发生频率可能​​较低,但他们仍然需要担心诸如锁定造成的停机时间之类的事情。他们需要在正确执行架构更改(例如GitLabPayPal),定制工具(例如MetaSquare)的内部指南,并且经常记录由模式迁移引起的事件或近事件(例如,GitHubDoctolibDoctolibGoCardless)。 P>

全面,开发人员抱怨架构变化影响了他们的速度:他们需要更多的沟通,更多的步骤,以及向后兼容的问题。结果,一些开发人员从不修改,也从未删除列,他们只添加新列。这创建了架构 - debtâ-创建错误,使新的团队成员感到困惑,并在其使用日期之前保持兼容性代码。

模式变化的问题

以下特定于PostgreSQL,因为这是我们在XATA上使用的数据库系统,但其中一些也适用于其他数据库系统。

锁定陷阱

PostgreSQL有许多different types of locks和大多数ALTER TABLE语句(但是not all)乘坐Table ACCESS EXCLUSIVE锁,它与所有其他锁定类型冲突。这意味着该桌子本质上是无法访问的,重要的是要保持最小可能的持续时间。

即使在短时间内锁定锁时,它仍然可以使桌子看起来不可用。在正常操作期间,读取和写入桌子同时运行。但是,当需要ACCESS EXCLUSIVE运行的ALTER TABLE查询时,Postgres需要创建一个开口,在该开口中没有其他查询或交易正在运行。为了实现这一目标,在ALTER TABLE完成后,将ALTER TABLE后发出的所有查询都排队运行。这是问题所在:如果您的表具有运行查询或运行交易,则无法开始模式更改。当Postgres正在等待运行模式更改时,您认为会立即运行的所有查询现在已排队。这使您的桌子看起来不可用。

避免上述内容的trick是在运行ALTER TABLE之前明确取锁,并设置一个lock_timeout,以避免长时间排队查询。如果超时打击,您将不断重试直到锁成功,然后执行ALTER TABLE

添加索引时使用魔术关键字CONCURRENTLY,但在交易中不起作用。当添加像NOT NULL这样的约束时,Postgres需要首先检查表中没有零件,并且在握住锁定时必须这样做。您可以通过使用NOT VALID添加CHECK CONSTRAINT来进行work around,这意味着该约束将其应用于新行,而不是旧行。然后,您可以稍后运行VALIDATE,因为它假设新行尊重约束。

,它不需要ACCESS EXCLUSIVE锁。

简而言之,PostgreSQL提供了控制锁定并避免长时间保持锁的好方法,但是可以说这是一个雷区。犯错误很容易,每个错误都可能是昂贵的。因此,团队需要拥有有关如何正确执行操作的内部文档,并在评论中要格外小心。分期系统有助于捕获某些陷阱,只要它们具有与POD数据库完全相同的数据和类似流量级别的数据,这通常不是这种情况。

Schema changes staging meme

应用程序部署和重命名的6个阶段

上一节主要适用于拥有大桌子的团队,但这适用于任何关心不破坏事情的团队。它也不是特定于后的。

架构更改不隔离,它们是包括应用程序代码更改的新功能或修复的一部分。如果我们可以在同一时刻将架构更改和应用程序代码部署到所有应用程序服务器,这将不是一个问题。但是,实际上,旧代码需要与新架构一起使用的时间窗口,或者相反。

正确做事的策略取决于您喜欢做出的变化。例如,如果添加一列:您将首先执行模式更改,回填数据,然后部署应用程序代码。之后,您将应用约束,这本身可能是一个多步过程。

如果要删除列,则相反。您首先确保无法访问该列,然后将其从架构中删除。

重命名呢?您将遵循以下步骤(PlanetScale docs的帽子提示):

  1. 创建一个带有新名称的新列。
  2. 更新并部署应用程序将数据写入两个列。
  3. 回填从旧列到新列的丢失数据。
  4. 可选地,一旦所有数据都被回填,
  5. 更新应用程序以仅使用新列,然后删除对旧列名称的任何引用。
  6. 放下旧列。

总而言之,为了正确更改模式,即使忽略锁定问题,您通常都需要一个多步骤过程。这两者都在放慢脚步,并且有一个较大的表面积以犯昂贵的错误。

回滚?回滚您的期望

如果有比执行架构更改更可怕的事情,那么您可能必须在时间限制下撤消它们。

Schema changes rollback choice meme

如果模式更改需要仔细考虑和多步骤过程,则将它们滚回去并不是更简单。通常,您必须小心地以相反顺序应用相同的步骤。

因为这很复杂并且需要很长时间,所以我们大多数人在开始模式迁移之前倾向于不测试回滚。这使它变得可怕。您可以使用数据库处于未知状态的情况,现在您需要贯穿一组未经测试的生产步骤。

对于那些确实测试过回滚的纪律发展的开发人员,您可能仍然被卡住了,等待很长时间才能完成回滚。这可能会让您盯着终端几分钟或几个小时,等待您的申请回到网上的用户。

如果我们不害怕模式变化怎么办?

我最喜欢在XATA上工作的部分之一是,我们可以采取这种类型的工作流问题并从第一原则中重新考虑它们。

让我们想象我们有一个魔杖。您的理想架构管理系统看起来如何?这是我的:

  • 无论架构类型如何
  • 架构更改是无锁或在最小的时间内取台锁。
  • 架构更改不会通过破坏申请代码而导致停机时间
  • 您可以在单步中应用所有类型的更改,或者最多几个步骤。
  • 模式更改是 undobable ,撤消很快。

简而

这可能吗?

第一个见解是,在架构更改方面,我们对应用程序代码放大了很多责任,以使数据库简单。如果我们将复杂性移动到数据库方面,我们将其实施一次,并且所有应用程序都受益于此。

我们不必将应用程序代码转发/向后兼容,而是使数据库架构向后兼容。该数据库可以同时服务旧的模式(更改之前)和新模式(更改之后)。您可以应用模式更改,并且旧代码和新代码都可以并行起作用,直到完成升级为止。

将其放在时间轴上,看起来像这样:

The timeline of a schema change + application roll-out.

  1. 启动模式更改,例如当PR合并时。它可能需要一些时间,直到新架构可用为止,因此应用程序部署还没有开始。
  2. 一旦准备好了新架构,应用程序部署就开始了。它可以是滚动重新启动,因此该应用程序的旧版本和新版本可能会在一段时间内共存。没关系,因为旧模式仍然可用。
  3. 一旦应用程序部署完成,就可以删除旧模式。但是,如果需要回滚,您可能想让它活一会儿。

如果在仍然可用的旧架构时需要回滚,则可以安全地滚动应用程序代码。从它的角度来看,该模式从未被修改。

在旧模式和新模式有效的时间段内,系统保留临时隐藏列,并使用视图来表示旧模式和新模式。新的插入和更新会自动升级或在两个模式之间降级”,因此应用程序的旧版本和新版本都可以正常工作。

Views are used to represent the old and the new schema to the application.

在上述情况下,基本列操作(添加/删除/重命名)都变得标准化且安全。不再需要安排模式更改,不再避免重命名或推迟删除未使用的列数周。

添加约束更具挑战性,因为旧模式和新模式可能发生冲突。例如,让您说您在列上添加了NOT NULL约束。如果一个新的INSERT越过具有NULL值的旧模式,则需要接受它,因为它尊重旧模式。但是,结果行不能在新的架构中暴露,因为它不尊重约束。在这种情况下,最好将新行隐藏在新的架构中,并阻止迁移到解决此问题。

据我所知,只有一个项目可以尝试与此相对的尝试:相对较新的Reshape。它使用Postgres视图来揭示模式的两个版本和触发器,以升级/降级新数据。它没有如上所述执行约束部分,但表明这种方法是可能的。结合Xata pull request based workflow,我认为上述理想系统是可能的!

下一步

如果您感觉到模式的痛苦发生了变化,并且认为必须有更好的方法,我们很喜欢与您交谈!我们计划以PostgreSQL的开源项目的形式进行此操作,如果您想在发布该项目时被通知,您可以在Twitter上关注我们,加入Discordsign up for Xata