首先,数据库升级是邪恶的,但 blog 描述了一场彻头彻尾的噩梦。
可以根据升级方法创建程序员能力矩阵:
- 0 级:根本没有升级。客户害怕并使用应用程序提供的 UI 或第三方数据库管理解决方案手动移动数据(相信我,这确实是可能的)。
- 级别 1:有升级数据库转储的脚本。客户感到安全,但他们将在未来 1-2 年内解决微小且非常恼人的问题。系统正在运行,但不允许更改。
- 2 级:表更改。巨大的停机时间,尤其是在升级过程中出现问题的情况下。巨大的问题,几乎不能保证获得 100% 安全的结果。数据转换由一个有缺陷的脚本管理。客户不高兴。
- 级别 3:无模式设计:一两个小时的停机时间让错误的脚本翻译数据库中的配置(此步骤在许多情况下可能会损坏数据库)。支持人员的所有咖啡储备都用完了。
- 4 级:延迟透明升级:零停机时间,但仍可能出现一些问题。客户几乎很高兴,但仍记得以前的经历。
- 5 级:理想架构,无需显式升级。总幸福。客户不知道升级程序是什么。开发人员富有成效且冷静。
我将描述所有技术问题,但在此之前让我声明以下内容(请原谅我的回答很长):
- 现在开发周期非常压缩,数据库很大
- 几乎任何功能都可能引入方案更改并破坏兼容性,因此我们要么有一个简单而稳定的升级过程,要么我们可能会推迟某个功能
- 客户可能会发现问题,因此有机会进行紧急热修复构建并需要一些升级步骤
- 一般来说,最好避免您和客户之间的任何障碍
0 级和 1 级
这两种情况都很明显和愚蠢。任何人都应该避免这种情况。
2级
更改对于小桌子来说并没有那么糟糕,但对于大桌子来说可能是个问题。在非常大的表 (>1Gb) 上,完成 ALTER TABLE 可能需要几个小时甚至几天。此外,它确实只解决了模式升级问题,但存储数据呢?我还建议考虑物理数据布局,以了解这种方法背后的实际障碍。整个过程可能不安全,因此请确保您有备份。
解决方案:
3 级
架构升级的问题是通过将架构移动到更高层来解决的。无模式解决方案有些有限,主要是因为它禁用了关系模型背后的全部功能。可以提出一种混合方法,以同时具有快速升级和使用关系代数的能力。有一些有趣的文章:
请注意,升级过程的复杂性仍然存在,只是移到了应用程序级别。有许多相关的场景,但我将描述一个我已经使用了几年的混合系统。我可以将数据模型描述为“具有关系的实体”。实体之间的关系在数据库级别表示,实体本身存储为 XML blob。
这个系统很成熟,有足够多的客户。有很多功能请求,所以研发和 QA 团队有点压力。最初的升级过程是作为一个独立的 Java 应用程序实现的,它从 DB 中读取 XML blob,使用 DOM API 对其进行升级并将其写回 DB。实际的方法看起来很简单,但背后有几个隐藏的问题:
- 升级逻辑可能有点错误,因此可能会写入错误的 XML 数据,从而显着增加客户的停机时间
- 读取-转换-写入 1-2GB 的 XML 可能需要一些时间
- 所有升级过程步骤都应包含自动化测试(我会说 CI 是必须的)
- 可能会在一两天内发现隐藏的故障,因此由于插入了新数据,因此备份不再有用
- 升级代码可能会变得有点混乱,尤其是如果您希望/需要在构建之间进行升级(任何敏捷团队的正常要求)
我已尝试通过使用更严格的升级程序定义、验证规则和 CI 系统针对真实数据(从所有客户收集)执行的广泛测试来降低所有潜在风险。由于旧升级脚本很久以前引入的旧问题,我很惊讶地看到一些步骤失败。为了解决隐藏的问题,开发了单独的升级步骤。还进行了一些优化以将升级时间减少到合理的 20-30 分钟。基于控制台的进度条实现完成了剩下的工作。
快速说明:任何最终用户都渴望看到任何长时间运行(>2 分钟)操作的进展。请不要忘记实现这样的“乐趣”。
最初 DB 版本存储在单独的表中。请不要使用这种方法,因为单独对实体进行版本化并避免在升级期间锁定整个数据库会更好。
将显示一个升级过程作为示例(所有验证和验证步骤都隐藏在<build/> 和<version/> 处理逻辑之后)。 '-' 表示更少,'*' - 任何构建
<?xml version="1.0"?>
<upgrade>
<version name="-7.4">
<build name="*">
<script class="upgrade.version7.Replace...Script"/>
<script class="upgrade.version7.Update...Script"/>
<!-- 5 scripts skipped -->
</build>
</version>
<version name="-7.6">
<build name="*">
<script class="core.DatabaseUpdateVersion" version="7.6.48"/>
</build>
</version>
<version name="7.6">
<build name="*">
<script class="upgrade.version7.Update...Script"/>
<script class="core.DatabaseUpdateVersion" version="8.0.40"/>
<!-- 7 scripts skipped -->
</build>
</version>
<version name="8.0">
<build name="-53">... </build>
<build name="+52">... </build>
</version>
<version name="8.1">
<build name="-8"> ... </build>
<build name="-9">...</build>
<build name="-26">...</build>
<build name="-40">...</build>
<build name="-45">...</build>
<build name="-56">...</build>
<build name="-61">...</build>
<build name="-63">...</build>
<build name="-64">...</build>
<build name="-68">...</build>
<build name="-69">...</build>
<build name="-77">...</build>
<build name="-79">...</build>
<build name="-80">...</build>
<build name="-86">...</build>
<build name="-88">...</build>
<build name="-89"> ... </build>
</version>
<version name="8.2">...</version>
</upgrade>
每个脚本都是一个小型 Java 或 Groovy 实现(也使用了 XSLT)。
后来还开发了降级程序,但这是完全不同的故事。
4级
应用层的数据方案允许做很多有趣的事情。例如,可以将 XML 替换为 protobuf。像往常一样,这样做有几个原因(它更简单、更快等)。如果您不喜欢建设者的概念,您可以改用thrift。
无论如何,protobuf 允许创建向后兼容的系统(就存储的数据而言)几乎不费吹灰之力。顺便说一句,优势不错。让您的系统向后兼容,您可以轻松实现惰性和完全透明的升级。它可以是后台进程或根据请求进行升级等。好消息是零停机、快乐的用户以及更频繁地进行升级的能力。这意味着您可以快速发展,及时响应客户的要求,换句话说,取得更大的成功。
5 级
抱歉,这次不行。请注意升级策略。销售一个定义了一些模式的系统并把自己锁在外面是很容易的。没有新功能——没有客户。
简单但非常有用的清单:
- 您能否及时解决来自客户端的问题?
- 客户升级系统是否安全(如果它至少是关键任务怎么办)?
- 确定问题需要多长时间?
- 是否有任何自动验证?
感谢您的阅读。