乐趣区

关于数据库:案例解读|高盛-Goldman-Sachs-如何做数据库变更

原文链接
作者|Shant Stepanian,高盛平台业务部的高级工程师


本文重点

  • Obevo 是在高盛开发的企业级数据库部署工具,在 2017 年以 Apache 2.0 许可证公布的开源我的项目。
  • 容许将数据库脚本按对象进行组织,相似于利用代码,对开发人员来说好处多多。
  • 能够帮忙具备现有数据库的新利用和零碎将其数据库更改治理纳入软件开发生命周期 (Software Development Life Cycle / SDLC) 管控。
  • 团队能够应用 Obevo 的上手工具和疏导示例疾速入门。
  • 其余性能包含回滚,内存数据库测试和分阶段部署。

近年,高盛采纳了规范的 SDLC 来构建和部署应用程序。这包含治理新零碎和现有零碎的数据库 schema,这比管理应用程序代码更加艰难。在本文中,咱们将形容咱们最近开源的数据库部署工具 Obevo 是如何帮忙高盛的企业级应用程序的数据库纳入 SDLC 管控的。

企业的数据库部署的问题

将数据库定义引入到规范的 SDLC 流程中是具备挑战性的,尤其是思考到数据库的状态以及执行增量迁徙的需要。因而,许多应用程序没有自动化或晦涩的数据库部署过程。咱们的指标是将数据库 schema 治理纳入与应用程序雷同的 SDLC 中:通过将所有定义提交到版本控制系统 (VCS) 并通过规范的构建 / 公布机制部署。

这项工作由咱们理论数据库系统应用案例的多样性而变得复杂:

  1. 古代零碎:全新的 schema 来部署表、进行内存测试,并从一开始就纳入了适当的 SDLC。
  2. 遗留零碎:超过十年的零碎,从未有过受控部署过程。
  3. 简单零碎:数百或数千个对象,包含表、视图、存储过程、函数、静态数据上传和数据迁徙等类型。
  4. 耗时零碎:蕴含数百万行的表,须要花几个小时部署。

无论应用何种案例,因为大量散布各地的开发人员都在进行变更,SDLC 自身都有简单之处。

尽管现有的开源工具能够解决简略的案例,它们无奈解决咱们一些现有零碎的规模和复杂度。然而咱们不能放任这些现有零碎没有适当的 SDLC:它们是正在开发和公布的要害零碎。

因而,咱们开发了 Obevo,一个能解决所有此类应用案例的工具。Obevo 的要害差异化因素在于可能按文件维护数据库对象(相似于更常见的按文件存储类定义),同时仍治理增量部署。

在本文中,咱们将探讨数据库部署问题,而后演示基于对象的我的项目构造如何帮忙咱们优雅地治理各种对象和环境类型的上百上千个 schema 对象。

数据库对象类型(有状态 vs 无状态)

首先,咱们来回顾一下不同数据库对象类型的部署语义,因为这会影响工具的设计。

疾速术语阐明:

  • 将某些代码 / SQL 利用于批改数据库的行为将被称为部署或迁徙。
  • 被部署的代码单元将被称为脚本片段。
  • 一个文件可能蕴含多个脚本片段,即脚本片段不等同于脚本文件。对于一个文件是否应该蕴含一个还是多个脚本片段是本文中的主题。

有状态对象(例如:表)

有状态对象须要增量批改其定义,而不是齐全的定义替换。以下是增加两列到 MyTable 中的示例:

现实状况下,咱们能够通过一个 SQL 语句来把数据库带到终态,该 SQL 定义了一个具备四列的表。不过 SQL 其实无奈提供可行的解决方案:

  1. 删除并从新创立表意味着你会失去数据
  2. 将数据保留在长期表中可能是一个低廉且简单的操作

相同,关系型数据库管理系统 (RDBMS) 容许用户应用 ALTER 语句来批改现有表。

某些列更新可能须要进行数据迁徙,例如从一个表中挪动列数据到另一个表中。

因而,每个对象都是利用多个脚本片段的后果;初始脚本片段创建对象,后续脚本片段批改对象。

无状态对象(例如:视图,存储过程)

另一方面,无状态对象能够通过指定其残缺对象定义来创立和批改。

  • 这里,DROP + CREATE 语句或 CREATE OR REPLACE 语句都实用
  • DROP + CREATE 在这里是平安的,因为这些对象没有数据 / 状态

咱们也将静态数据文件(代码或参考数据表)视为无状态对象。尽管它波及到表数据,然而这些数据在你的脚本中曾经齐全定义,并且能够通过批量 delete + insert 或选择性 insert + update + delete 操作部署到表中。

数据库部署工具准则

Martin Fowler 的 Evolutionary Database Design 对基于源头管制的数据库部署工具遵循的次要准则进行了精彩论述,上面是其中一些具体相干的要点。

  1. 所有数据库制品 (Database Artifacts) 都与利用程序代码一起进行版本控制(与从 UI 治理相同)

基于 UI 的治理可能实用于非技术用户,但咱们倡议开发团队将其数据库脚本片段存储在源代码管制中(和看待其它应用程序一样),并以自动化形式调用部署。

  1. 对于有状态对象,明确编码的增量迁徙比主动计算的迁徙更可取。

在咱们看来,在企业环境中,例如在以后数据库表状态和代码残缺视图之间主动计算迁徙是有危险的。

数据库部署工具要求

咱们依据以下需要评估数据库部署工具的解决能力:

A) 将增量更改部署到现有数据库中

这是数据库部署工具的次要性能;大多数生产环境都是通过此形式执行的。在一些非生产环境中,尤其是在公布到生产之前的 QA 环境中进行测试时,也会应用此办法。

B) 将残缺 schema 部署到空白数据库中

开发人员可能心愿将其部署到一个空白沙盒数据库以进行以下操作:

  1. 验证 SQL 脚本片段是否理论可用;
  2. 运行波及你数据库(例如测试新列或存储过程)的系统集成测试;
  3. 在应用内存数据库时,在单元测试中测试数据拜访代码。

这能够通过以下几种形式来实现:

  1. 通过从头开始重放所有迁徙脚本片段,前提是先前的脚本片段已保留在你的包中
  2. 从新基线化脚本片段,使其能够部署到空白数据库,同时依然容许后续增量生产部署。

C) 易于保护和浏览

在咱们进行数据库部署改良之前,咱们看到一些团队为每个数据库对象保护一个文件,其中蕴含对象的定义,只管这些文件并未用于部署。

这仿佛没有意义,但咱们取得了一些见解:

  • 开发人员喜爱可视化地看到他们的数据库对象和构造的示意模式
  • ORM 工具如 Hibernate and Reladomo 生成显示此类构造的 DDL,并将其与你的数据库实例分割起来是一个加分项。

通用数据库部署工具设计

次要部署算法

根据上述准则,大多数基于源代码管制的数据库部署工具(包含 Obevo)的工作形式如下:

  1. 开发人员为下一个版本编写脚本片段,增加到曾经在源代码管制中部署的脚本集中。
  2. 测试并将脚本片段,构建成一个软件包。
  3. 软件包针对指标数据库进行部署。

    1. 简略的工具须要部署者指定要部署的脚本集。
    2. 进阶的工具通过与部署日志表进行比照来确定哪些脚本片段须要进行部署,如下图所示
    1. 这样雷同的软件包和部署命令可针对任何环境进行应用,无论先前在该环境上部署了哪个版本

有状态和无状态对象部署语义

对象类型对变更集计算语义会产生影响。

  • 无状态对象容许增加、删除和更新脚本:如果该对象定义是无效的,则能够替换数据库中现有的定义而不会失落数据。
  • 然而,有状态对象通常只容许增加脚本:更新已部署的脚本可能会造成与预期不同的对象。

为了演示有状态用例,咱们将 deploy 部署包 v1 以获取右侧表。

假如有人批改了 M3 并重命名了列,而后咱们进行重新部署。咱们冀望会产生什么?

工具检测到不匹配:

  • 脚本片段 M3 已更改并想要增加列 C123456。
  • 然而数据库曾经部署了列 C。
  • 源脚本片段不再包含列 C,但也无奈从数据库中删除它。

因而,个别规定是:有状态对象脚本一旦部署就不能批改。

某些抉择性功能能够让咱们在须要时绕过这个问题,例如:

  • 回滚:提供一个明确的回滚脚本片段,以在勾销部署时应用
  • 从新基线化:将已部署的脚本重写为更简洁的脚本而不尝试重新部署

数据库部署工具的实现抉择

思考到它们的底层算法类似,部署工具因几个实现要点而异。

1)如何将脚本片段组织成文件

有如下几种形式将脚本片段进行分组:

  • 公布的版本
  • 批改的对象
  • 不对脚本进行分组,并保留独自的迁徙

2)如何排序部署要部署的脚本片段

须要思考:

  • 列出准确迁徙程序的独自文件
  • 确定程序的文件命名约定
  • 对暗示程序的脚本汇合进行依赖性剖析

接下来,咱们将具体介绍 Obevo 是如何解决这两个问题的。

Obevo 设计:基于对象的脚本组织

咱们次要的数据库部署问题是如何治理 schema 中大量对象的开发、保护和部署。同时,还有开发人员在编写应用程序时解决他们的数据库对象。

因而,咱们心愿提供一种对开发人员来说易于了解的体验,这导致了咱们依据对象名称组织脚本。在本节中,咱们将深入探讨这些详细信息。(该构造减少了一些排序方面的挑战,下一节具体介绍)

我的项目构造根底

咱们依据实用于那些对象的脚本片段来组织,以下是一个示例。

文件构造依据对象是否具备状态而不同。

  • 无状态对象能够仅将定义自身存储在文件中,因为它们的残缺定义能够针对数据库进行部署。
  • 然而,有状态对象须要应用增量脚本片段进行部署。

    • 因而,须要多个脚本片段能力将一个对象带到其以后状态,并且咱们将所有这些内容保留在同一文件中。
    • 咱们通过以 //// CHANGE 结尾的行来标记每个局部并将文件拆分成多个脚本片段。

剖析:无状态对象解决

基于对象的构造对于无状态对象不便很多,因为残缺的无状态对象定义能够在文件中保护,并且能够就地批改。

作为比拟,在增量有状态的形式中,技术上能够解决无状态对象部署,例如作为长久化多个版本的增量脚本。然而,这会导致数据库脚本存在冗余,因为对象在多个版本中发生变化。

剖析:可读性

从保护的角度来看,这个我的项目构造有一些劣势:

  • 数据库构造从我的项目构造中易于查阅。
  • 要更改或审核对象,查找文件很不便。
  • 编写或审核更改的同时能够查看对象定义,而不是在其余文件或数据库自身中查看对象定义。
  • 只管脚本片段可能会在有状态的对象文件中累积,但能够通过从新基线化性能将多个脚本合并为一个,而不执行任何部署来缓解。

为了进行比拟,能够拿一个我的项目构造的例子来阐明,其中一个文件与迁徙或公布相关联,因为许多工具都反对这种形式。这可能会导致一些问题:

  • 对象可读性有余:如果一个对象在多个版本中被批改,它的构造将会扩散在多个文件中(对于有状态的对象),或者仅仅是在许多文件中冗余存在(如之前所示的无状态对象)。
  • 无奈读取和写入对象的累积:因为上一条而无奈读取,依据不批改有状态对象脚本的规定而无奈写入
  • 尽管从新基线化能够缩小文件数量,但必须在残缺 schema 下执行,而不是按对象进行。但与基于对象的我的项目构造相比:

    • 从新基线化工作量将更大
    • 生成的新基线文件将更大且难以浏览

从代码审查 / 公布审查角度来看:面向对象的构造意味着特定版本中所有更改都会扩散在文件中。乍一看,仿佛很难审核要部署到下一个版本的脚本片段。然而,咱们依然能够通过比拟 VCS 历史记录和标签来审核公布的代码 – 就像解决利用程序代码一样。

剖析:对于开发者的益处

应用 Obevo 我的项目构造,开发人员也好处多多。

因为对象的脚本片段搁置在单个文件中,咱们能够轻松地在测试中部署单个对象,这对于像在内存数据库中进行数据拜访 API 的单元测试等状况十分有用。

开发人员也能够利用 ORM 工具从应用程序生成 DDL,并与迁徙脚本进行协调。简洁起见,这里咱们不深刻探讨,但你能够在文档中理解更多。

Obevo 设计:通过依赖性剖析进行排序

尽管抉择基于对象的我的项目组织形式提供了上一节中提到的许多益处,但它也使排序变得更加简单。

对象能够相互依赖,随着咱们把 schema 扩大到数百或数千个对象,手动指定程序变得越来越艰难。

让咱们形容一下咱们是如何克服这些挑战的。

排序算法

不是所有的脚本片段都相互依赖,因而咱们在对象依赖申明的显著束缚下是有一些灵活性的。

因而,咱们应用一个简略的图算法来设计解决方案。

比方以下示例语句:

  • 3 条用于创立表
  • 1 条用于建设一个外键
  • 1 条用于创立视图:

留神以下几点:

  • 建表的程序并不重要
  • 外键必须在 TABLE_A 和 TABLE_B 之后创立。
  • VIEW1 必须在 TABLE_A 之后创立。

这适宜应用有向图示意,其中图节点是脚本片段,边是程序依赖关系。

咱们当初能够应用拓扑排序算法得出一个可承受的程序,以放弃这些程序束缚并胜利部署咱们的数据库。

拓扑排序能够产生许多可承受的程序,然而咱们会调整算法应用形式以给出繁多统一的程序,以便在各个环境中具备一致性。

当初最初一个细节:如何在脚本片段中申明依赖关系?

依赖申明和发现

咱们发现最简略的办法是在脚本中申明依赖项。请参见上面 TABLE_B.fkA 脚本片段中的 dependencies 属性。

然而,对于大型数据库来说这并不敌对(设想一下正文数百或数千个对象),因而咱们须要一种自动检测依赖关系的形式,同时仍容许开发者笼罩。

咱们应用两种策略来推断依赖关系:

有状态迁徙的外部对象依赖关系:

咱们容许有状态的对象定义多个脚本。很天然地,咱们认为在同一个文件中编写的迁徙依照它们编写的程序进行部署,因而咱们推断出这样的依赖关系(下图)。

通过文本搜寻实现跨对象依赖关系

为检测跨对象的依赖关系,咱们须要搜寻脚本内容以查找相干对象。

从技术上讲,现实的办法是解析 SQL 以查找这些对象。然而,这十分艰难,因为咱们必须了解所有反对的 DBMS 类型的 SQL 语法。

相同,Obevo 采纳简略的办法:在你的我的项目中抉择通过字符串搜寻发现的对象名称,并假设它们是依赖项。

实现阐明:

  • 能够通过列出我的项目中的文件来查找可用的对象名称
  • 有多种办法能够在脚本中搜寻对象名称。咱们以后的实现通过空格将脚本合成为标记,而后查看该标记是否存在于对象名称汇合中
  • 算法还有更多细节,但以上内容足以阐明

以下是咱们先前示例的算法后果:

如果呈现误报匹配(例如因为正文)或假阴,开发人员能够依据须要指定排除或蕴含笼罩。

乍一看,可能很难设想这实用于理论用例,但咱们曾经胜利地应用了这种技术来部署许多简单的 schema,其中一些逾越数千个对象,如表、存储过程、视图等。

想看实例的话请查看咱们的 kata 课程,该课程是个通过一个大型数据库 schema 反向工程的示例。

跨多个表处理数据迁徙

咱们疾速过一下这种用法(行将数据从旧列挪动到新列,而后删除旧列),因为最开始将基于对象的文件构造概念利用于此听起来更简单。

Obevo 能够解决这个问题 – 简而言之,咱们提供了「迁徙」对象的概念来帮忙解决此问题,它:

  1. 让咱们定义更新脚本片段以促成这些迁徙
  2. 容许每个对象脚本仅保留与其定义相干的脚本片段,从而保留其独自部署进行测试的能力

如需更多信息,请查看文档。

现有数据库 schema 的逆向工程

心愿咱们胜利展现了你能够应用 Obevo 部署简单的数据库 schema。然而,为了让现有零碎真正退出到 Obevo 中,咱们必须使开发人员可能轻松地对现有数据库进行逆向工程。

解决这个问题并不容易,因为可怜的是,在不同的 DBMS 类型之间没有一个完满的对立 API

  • Java + JDBC 提供 DatabaseMetaData API,但在不同的 DBMS 上实现形式存在差别
  • 一些第三方工具试图弥合差距,但不能涵盖厂商可能公开的所有具体 SQL 语法,并且可能滞后于笼罩 DBMS 公布的新性能

因而,咱们抉择与供应商提供的反向工程工具集成(见下表)。一些工具只需将残缺架构输入到单个文件中即可,但咱们提供了一个实用程序,能够将这些文件转换为 Obevo 基于对象构造,并利用简略字符串解析和正则表达式技术。相比 Java API,咱们认为这种技术更牢靠于现有零碎中,特地是外围供应商工具最懂得他们本人架构。

数据库管理系统

论断

尽管有许多开源工具可用于数据库部署,但咱们认为更简单的应用状况须要更弱小的工具反对。

通过 Obevo,咱们旨在反对所有类型的零碎;无论是通过测试性能和易于基于对象保护来加强古代零碎的生产力,还是通过促成长期存在但以前没有 SDLC 管控的零碎退出管控重获新生。

这里还有更多未波及到的个性和数据库部署流动(例如回滚、分阶段部署、内存 DB 测试、多 schema 治理)。欢送拜访咱们的 Github,文档和 Kata 课程,理解更多对于 Obevo 以及如何将该工具利用到你的零碎中。


Bytebase 团队的读后感

通过解读高盛如何变更数据库的案例,咱们能够一窥企业级数据库变更的复杂度。这篇文章的精髓在于当中对于数据库变更工具设计方案抉择的亮点演绎:

  1. 如何将脚本片段组织成文件
  2. 如何排序部署要部署的脚本片段

Obevo 相比于业界其余的工具,在这两点上都抉择了非主流,然而能够更加自动化的计划:

  1. 在脚本的组织上,采纳了面向数据库对象的组织
  2. 在部署排序上,采纳了隐式的自动化拓扑排序

对于高盛来说这是可行的,因为在一个繁多组织内,能够通过各种束缚,保障操作的一致性,而后基于这样的对立,进步自动化。然而如果要作为一个对外输入的计划,就未必能应酬不同组织不同的操作习惯。
Bytebase 团队接触到 Obevo 也是一个潜在客户在调研各种计划时聊起的。相比于 Obevo 而言,Bytebase 提供的是数据库变更,查问,平安,治理一体化计划,而且还提供了可视化界面。


💡 你能够拜访官网:https://www.bytebase.com/,收费注册云账号,立刻体验 Bytebase。

退出移动版