案例解读|高盛 Goldman Sachs 如何做数据库变更
原文|https://www.infoq.com/articles/Obevo-Introduction/
作者|Shant Stepanian(https://www.infoq.com/profile/Shant-Stepanian/) 是高盛平台业务部的高级工程师。
本文重点
Obevo(https://github.com/goldmansachs/obevo) 是在高盛开发的企业级数据库部署工具,在 2017 年以 Apache 2.0 许可证发布的开源项目。
允许将数据库脚本按对象进行组织,类似于应用代码,对开发人员来说好处多多。
可以帮助具有现有数据库的新应用和系统将其数据库更改管理纳入软件开发生命周期 (Software Development Life Cycle / SDLC) 管控。
团队可以使用 Obevo 的上手工具和引导示例快速入门。
其他功能包括回滚,内存数据库测试和分阶段部署。
近年,高盛采用了标准的 SDLC 来构建和部署应用程序。这包括管理新系统和现有系统的数据库 schema ,这比管理应用程序代码更加困难。在本文中,我们将描述我们最近开源的数据库部署工具 Obevo 是如何帮助高盛的企业级应用程序的数据库纳入 SDLC 管控的。
企业的数据库部署的问题
现代系统:全新的 schema 来部署表、进行内存测试,并从一开始就纳入了适当的 SDLC。
遗留系统:超过十年的系统,从未有过受控部署过程。
复杂系统:数百或数千个对象,包括表、视图、存储过程、函数、静态数据上传和数据迁移等类型。
耗时系统:包含数百万行的表,需要花几个小时部署。
数据库对象类型(有状态 vs 无状态)
将某些代码 / SQL 应用于修改数据库的行为将被称为部署或迁移。
被部署的代码单元将被称为脚本片段。
一个文件可能包含多个脚本片段,即脚本片段不等同于脚本文件。关于一个文件是否应该包含一个还是多个脚本片段是本文中的主题。
有状态对象(例如:表)
删除并重新创建表意味着你会失去数据
将数据保存在临时表中可能是一个昂贵且复杂的操作
无状态对象(例如:视图,存储过程)
这里,DROP + CREATE 语句或 CREATE OR REPLACE 语句都适用
DROP + CREATE 在这里是安全的,因为这些对象没有数据/状态
数据库部署工具原则
所有数据库制品 (Database Artifacts) 都与应用程序代码一起进行版本控制(与从 UI 管理相反)
对于有状态对象,明确编码的增量迁移比自动计算的迁移更可取
数据库部署工具要求
A) 将增量更改部署到现有数据库中
B) 将完整 schema 部署到空白数据库中
验证 SQL 脚本片段是否实际可用;
运行涉及你数据库(例如测试新列或存储过程)的系统集成测试;
在使用内存数据库时,在单元测试中测试数据访问代码。
通过从头开始重放所有迁移脚本片段,前提是先前的脚本片段已保存在你的包中
重新基线化脚本片段,使其可以部署到空白数据库,同时仍然允许后续增量生产部署。
C) 易于维护和阅读
开发人员喜欢可视化地看到他们的数据库对象和结构的表示形式
ORM 工具如 Hibernate and Reladomo 生成显示此类结构的 DDL,并将其与你的数据库实例联系起来是一个加分项。
通用数据库部署工具设计
主要部署算法
开发人员为下一个版本编写脚本片段,添加到已经在源代码控制中部署的脚本集中。
测试并将脚本片段,构建成一个软件包。
软件包针对目标数据库进行部署。
这样相同的软件包和部署命令可针对任何环境进行使用,无论先前在该环境上部署了哪个版本
简单的工具需要部署者指定要部署的脚本集。
进阶的工具通过与部署日志表进行对比来确定哪些脚本片段需要进行部署,如下图所示
有状态和无状态对象部署语义
无状态对象允许添加、删除和更新脚本:如果该对象定义是有效的,则可以替换数据库中现有的定义而不会丢失数据。
然而,有状态对象通常只允许添加脚本:更新已部署的脚本可能会形成与预期不同的对象。
脚本片段 M3 已更改并想要添加列 C123456。
但是数据库已经部署了列 C。
源脚本片段不再包括列 C,但也无法从数据库中删除它。
回滚:提供一个明确的回滚脚本片段,以在取消部署时使用
重新基线化:将已部署的脚本重写为更简洁的脚本而不尝试重新部署
数据库部署工具的实现选择
发布的版本
修改的对象
不对脚本进行分组,并保留单独的迁移
列出精确迁移顺序的单独文件
确定顺序的文件命名约定
对暗示顺序的脚本集合进行依赖性分析
Obevo 的设计:基于对象的脚本组织
项目结构基础
无状态对象可以仅将定义本身存储在文件中,因为它们的完整定义可以针对数据库进行部署。
然而,有状态对象需要使用增量脚本片段进行部署。 因此,需要多个脚本片段才能将一个对象带到其当前状态,并且我们将所有这些内容保存在同一文件中。
我们通过以 `//// CHANGE` 开头的行来标记每个部分并将文件拆分成多个脚本片段。
分析:无状态对象处理
分析:可读性
数据库结构从项目结构中易于查阅。
要更改或审核对象,查找文件很方便。
编写或审核更改的同时可以查看对象定义,而不是在其他文件或数据库本身中查看对象定义。
尽管脚本片段可能会在有状态的对象文件中累积,但可以通过重新基线化功能将多个脚本合并为一个,而不执行任何部署来缓解。
对象可读性不足:如果一个对象在多个版本中被修改,它的结构将会分散在多个文件中(对于有状态的对象),或者仅仅是在许多文件中冗余存在(如之前所示的无状态对象)。
无法读取和写入对象的累积:由于上一条而无法读取,根据不修改有状态对象脚本的规则而无法写入
虽然重新基线化可以减少文件数量,但必须在完整 schema 下执行,而不是按对象进行。但与基于对象的项目结构相比:
重新基线化工作量将更大
生成的新基线文件将更大且难以阅读
分析:对于开发者的好处
Obevo 设计:通过依赖性分析进行排序
排序算法
3 条用于创建表
1 条用于建立一个外键
1 条用于创建视图:
建表的顺序并不重要
外键必须在 TABLE_A 和 TABLE_B 之后创建。
VIEW1 必须在 TABLE_A 之后创建。
依赖声明和发现
有状态迁移的内部对象依赖关系
通过文本搜索实现跨对象依赖关系
可以通过列出项目中的文件来查找可用的对象名称
有多种方法可以在脚本中搜索对象名称。我们当前的实现通过空格将脚本分解为标记,然后检查该标记是否存在于对象名称集合中
算法还有更多细节,但以上内容足以说明
跨多个表处理数据迁移
让我们定义更新脚本片段以促进这些迁移
允许每个对象脚本仅保留与其定义相关的脚本片段,从而保留其单独部署进行测试的能力
现有数据库 schema 的逆向工程
Java + JDBC 提供 DatabaseMetaData API,但在不同的 DBMS 上实现方式存在差异
一些第三方工具试图弥合差距,但不能涵盖厂商可能公开的所有详细 SQL 语法,并且可能滞后于覆盖 DBMS 发布的新功能
结论
Bytebase 团队的读后感
如何将脚本片段组织成文件
如何排序部署要部署的脚本片段
在脚本的组织上,采用了面向数据库对象的组织
在部署排序上,采用了隐式的自动化拓扑排序
SQL Chat 更新 - 用户注册,早鸟套餐,GPT-4,新增支持数据库
Star History 月度开源精选|2023 年 4 月