从 MongoDB 到 PostgreSQL 的大迁移
原文 https://infisical.com/blog/postgresql-migration-technical,文章来自 infisical,是一家做密钥管理的开源商业公司。主要对标的是 HashiCorp Vault。
Infisical 在过去一年里迅速发展,平台现在每天处理超过 5000 万个密钥,将应用程序配置和私密数据发送给需要的团队、CI/CD 流水线以及服务器/应用程序。
随着使用量的持续增长,我们不得不不断升级我们的技术栈。最近,Infisical 进行了一次全面的数据库迁移,从MongoDB 迁移到 PostgreSQL。这涉及到对此项计划的深思熟虑、采用新技术、创建新的数据库 schema、重构逻辑、重写查询语句,以及将数百万(如果不是数十亿)的数据库记录迁移到 PostgreSQL。这是一个复杂的过程,但无论如何都是必要且有利于平台改善的步骤。
以下是我们决定从 MongoDB 转向 PostgreSQL,并说明了我们如何做到这一点背后所作出决策的故事。希望这篇文章能够引人入胜,并对那些可能某天会考虑进行类似数据库迁移操作的人有所帮助。
我们从何开始?
当我们首次构建 Infisical 时,我们选择了团队最熟悉的技术栈来构建。作为这个技术栈的一部分,我们选择了 MongoDB + Mongoose ORM,因为这种组合带来的额外负担最小,并且允许我们快速地发布高质量的功能。正如 Tony Hoare 爵士所说,
过早优化是万恶之源
当时确实没有进一步优化的需要。我们更专注于构建 Infisical Cloud,我们的托管 SaaS 服务。并且鉴于这个方向,我们并未预料到会有那么多用户自行托管 (self-host) 产品,因此我们并未针对该用例进行设计。
为何抛弃 MongoDB?
虽然 MongoDB 在早期为 Infisical 提供了良好的服务,但当我们的产品场景不单单是托管服务时,它开始显示出不足之处。随着时间的推移,我们发现许多组织,特别是那些在合规和安全交叉点运营的组织,更喜欢自托管 Infisical 而非使用 Infisical Cloud;其他的有一些本地需求需要满足。
随着对自托管 Infisical 的需求增长,我们发现自己正在开发许多功能以减少学习如何自我托管 Infisical 所需的曲线,并且作为其中一部分,我们最终放弃了 MongoDB 转而支持 PostgreSQL。
实际上,在能力和可用性方面,我们和客户经常遇到 MongoDB 带来的限制问题,比如缺乏对事务、清理、云托管产品中版本一致性的支持,更不同提与 schema-less 数据库设计结构相关联的问题了。
配置数据库事务困难:使用MongoDB,设置事务并不简单,因为它需要在集群模式下运行 MongoDB,并有各种配置开销;这使得客户要运行一个简单的 Infisical POC 变得极其困难,因为它需要生产环境下的 MongoDB 设置。对于处理高度敏感数据且数据完整性必须保证的产品来说,这是无法接受的。 缺少关系型特性:使用 MongoDB 后,我们失去了许多来自关系型世界的好功能,比如 CASCADE
,当目标资源被删除时,可以删除其他表中所有被引用资源;这尤其令人痛苦,因为我们的数据非常依赖关系型。结果是,在我们旧代码库中采用了大量删除函数却从未能完全完成任务,并在 MongoDB 数据库中留下悬空资源。云提供商支持不足:在 MongoDB 将许可更改为 SSPL 后,许多云提供商选择提供较老版本的 MongoDB。结果是,我们发现很难确保 Infisical 的功能可用性, 除非是运行最新稳定版 MongoDB 的客户。 缺乏操作 MongoDB 的经验:由于更多人熟悉部署基于 SQL 的数据库, 他们经常在扩展和正确配置 MongoDB 上遇到困难;这导致我们需要为客户提供的支持量不成比例地增加,特别是因为他们对 MongoDB 不熟悉。
为何钟情 PostgreSQL?
集成存储:我们可以将像 SQLite 这样的数据库系统直接打包到 Infisical 中,并采用水平复制策略来通过避免额外的网络跳转来减少延迟。在这个模型中,扩展系统意味着部署多个 Infisical 实例,并让它们通过某种共识算法(如Raft)进行相互通信。虽然这看起来是一个极好的解决方案,因为客户不需要连接任何依赖项就能运行Infisical,但执行此愿景所需的工具生态系统感觉还不够成熟,而且所需的工程努力也太强人所难了。 外部存储:我们可以简单地用 PostgreSQL 或 MySQL 等其他数据库替换 MongoDB,并使用其内置的扩展功能。尽管这个解决方案并没有完全消除使用 Infisical 时需要外部依赖项带来的摩擦,但我们认为它已经通过不再是MongoDB,而带来了显著的优点。当涉及到支持一个或多个数据库时,我们认为支持多个会错过每种解决方案各自独特的优点;同时也会增加我们的工程开销。
ORM 该怎么办?
我们是如何计划迁移的?
大迁移
在迁移的几周前,我们会通过电子邮件和应用内横幅提前通知用户即将进行的数据库升级。我们将对平台上的每个功能流程进行彻底测试,并为迁移进行试运行。
迁移本身将在一个六小时的窗口期内发生,在此期间只允许对平台进行读取操作。在这个窗口期,我们会运行迁移脚本,把数据从 MongoDB 转移到 PostgreSQL,检查是否有数据丢失,并且如果成功就把 DNS 切换到新实例。当然,如果事情出现问题,我们已经备好了应急方案。
最后,在迁移之后,我们会解决任何残留问题,并开始推出 Infisical 和 PostgreSQL一起工作的新文档。
结果
平台的性能大幅提升,主要归因于对连接查询的优化。使用MongoDB时,平台经常需要进行效率低下的聚合查询和多次网络跳转以实现所需功能。例如,由于我们核心数据的关系特性,我们经常需要执行许多
$lookup
操作来模拟 SQL 中的连接;这些操作效率低下,并且通常需要我们相应地扩展数据库和应用实例。迁移到PostgreSQL 后,我们避免了这些效率低下的操作,也使得数据库账单上的成本减少了50%。现在的平台采用更好的数据验证方式,在数据库层面而不是应用层面进行根源处理。由于 MongoDB 设计上没有 schema 约束, 它依赖 Mongoose 框架定义数据类型、必填字段和验证规则。有了 PostgreSQL, 我们再也不会面临如果数据库被绕过 Mongoose 范围访问或修改时可能出现的数据不一致问题。
最后并且最重要的是, 我们认为 Infisical 现在更容易自我托管, 客户可以无需额外配置开销 (如处理 MongoDB 中启用事务功能所需的 replica sets) 就能进行 POC 测试. 总体来说, 考虑到手头目标、任务范围及其执行结果, 我们认为这项举措非常成功。一旦我们有更多数据,我们计划在未来发布更具体的结果。