Infisical 是一家开源的密钥管理平台,为团队及基础设施提供同步密钥的服务并防止密钥泄露。随着业务不断发展、数据不断增加,原有数据库已无法满足当前需求,Infisical 决定从 MongoDB 迁移到 PostgreSQL 。本文将分享这一从非关系型数据库迁移至关系型数据库的决策判断及背后的迁移故事。
Infisical 在过去一年里迅速扩张,目前平台每天处理超过 5000 万个密钥,向需要数据的团队、CI/CD 管道以及服务器/应用程序,发送应用程序配置和机密数据。
随着使用量不断增加,我们必须持续升级技术栈。最近,Infisical 进行了从MongoDB到PostgreSQL的数据库全面迁移。此次迁移涉及到对方案的审慎评估、采纳新技术、创建新的数据库模式(schema)、重构逻辑、重写查询语句,将数百万(如果不是数十亿)的数据库记录迁移到 PostgreSQL。这过程十分复杂,但也是改善平台的必要一步。
本文将介绍我们从 MongoDB 迁移到 PostgreSQL 决策背后的故事以及迁移过程。希望这篇文章令人感兴趣,并为其他考虑进行类似数据库迁移的人提供帮助。
从何开始
最初构建 Infisical 时,我们选择了团队最熟悉的技术栈进行构建。作为这个方案中的一部分,我们选择了 MongoDB + Mongoose ORM, 因为这种组合的开销最小,并提供了快速交付高质量的功能。
正如托尼·霍尔爵士所说,“过早优化是万恶之源”,也确实没有进一步优化的必要,我们当时还专注于构建 Infisical Cloud(托管 SaSS 产品)。同时由于这一产品侧重点,我们并没有预计到有很多用户自行托管产品,所以也没有对这一用例设计相关方案。
为什么放弃 MongoDB?
虽然 MongoDB 在早期为 Infisical 提供了良好服务,但当我们产品场景发展到超出托管服务范围时,它开始显示出不足。随时间推移,我们发现许多组织,尤其是在合规性和安全性的交叉领域运营的组织,相较使用Infisical Cloud,更喜欢自托管 Infisical;同时用户也有些需要满足的本地需求。
随着对自托管 Infisical 的需求不断增长,我们发现已经开发了相当多功能来减少自托管 Infisical 所需的学习曲线,而在这一趋势的影响下,我们最终放弃了 MongoDB,转而使用 PostgreSQL。
在实践中,我们和我们的客户经常在功能和可用性方面受到 MongoDB 的限制,例如缺乏对 事务、清理的支持、云厂商托管产品的版本控制不一致,更不用说无模式(schema-less)数据库设计结构等相关问题。
下面详细阐述其中的一些挑战:
配置数据库事务困难:使用MongoDB设置事务并不容易,因为它需要在集群模式下运行 MongoDB,并产生各种配置开销。由于需要MongoDB 的生产设置,客户运行简单的 Infisical POC 变得极其困难。对于处理高度敏感数据且必须保证数据完整性的产品来说,这是难以接受的。
缺少关系型功能:使用 MongoDB,意味着失去了关系型世界中的许多优秀功能,例如 CASCADE ,每当删除目标资源时,就可以删除其他表中引用的所有资源;这造成了相当大的损失,因为我们的数据很大部分是关系型。所以,我们在旧代码库中使用了大量的删除函数,但这些函数从未完全完成工作,并在 MongoDB 数据库中留下了悬置资源。
缺乏跨云提供商的支持:在 MongoDB 将许可证更改为 SSPL 后,许多云提供商选择提供旧版本的 MongoDB。因此我们发现,除了使用最新稳定版本MongoDB 的客户外,很难确保运行在其他版本上 Infisical 的功能可用性。
MongoDB 实操经验不足:由于更多人熟悉部署基于 SQL 的数据库,他们常常难以扩展和正确配置 MongoDB;这导致我们需要为客户提供的支持量大幅增加,特别是因为他们不熟悉 MongoDB。
除了以上及其他原因之外,我们还意识到,将完整的数据库迁移到更通用的东西是让世界各地的团队和组织更容易使用 Infisical 所需的最终功能。
选择 PostgreSQL 的原因?
在寻找新数据库时,我们先列出最看重的几个方面:易于管理(即包括配置、部署和扩展)、内置事务支持以及关系型功能。我们还考虑了是否应该构建自己的集成存储或寻求外部存储解决方案。
以下是备选方案:
集成存储:可以将SQLite等数据库系统直接打包到 Infisical 中,并采用水平复制策略,通过避免额外的网络跳转来减少延迟。在此模型中,扩展系统意味着部署多个 Infisical 实例,并让它们通过Raft 等 共识算法相互通信。虽然这似乎是一个出色的解决方案,因为客户不需要连接任何依赖项就能运行 Infisical,但执行此愿景的工具生态系统还不成熟,而且所需的工程工作有些强人所难。
外部存储:可以简单地将 MongoDB 替换为其他数据库(例如 PostgreSQL 或 MySQL),并使用其内置的扩展功能。尽管该解决方案并没有完全消除 Infisical 使用时需要外部依赖项的问题,但我们认为这项方案由于不使用 MongoDB 而带来了显著优点。讨论到支持一个或多个数据库时,我们认为支持多个数据库意味着会失去各个解决方案的独特优势,同时增加的工程开销。
经过慎重考虑,我们选择了PostgreSQL。除了拥有充满活力的社区、广泛的文档以及众多可用的解决方案和扩展之外,我们最欣赏它的开源特性和绝大多数云提供商都支持 PostgreSQL 托管服务。
最重要的是,这意味着 Infisical 的用户可以更轻松地在任何云提供商上自行托管我们的平台,并将其与相应的托管 PostgreSQL 服务配对。此外,鉴于 PostgreSQL 已得到广泛采用,我们相信用户在使用 Infisical 时操作会更加便捷。
如何处理 ORM?
选择 PostgreSQL 后,我们需要弄清楚应用程序如何与数据库交互。我们第一反应是找到一些与使用 Mongoose ORM 的 MongoDB 体验类似的工具。因此,我们开始根据成熟度、可视化和迁移支持以及适当的抽象级别来评估候选工具;我们主要考虑了 Drizzle ORM、 Prisma ORM、 TypeORM和 Knex.js (一种查询构建器)。
最后,我们决定使用 Knex.js而非 ORM ,以便更好地控制数据库。无可否认的是,原始 SQL 是最通用的且抽象最少,但我们认为这种方法太容易出错,而且坦白说,在没有适当的TypeScript 支持的情况下,维护起来很麻烦。
此外,除了接近裸露的 SQL 之外,Knex.js 还自带用于播种和迁移工具包,拥有成熟的生态系统,其中包含出色的文档和几乎任何可能查询的答案。再加上一些定制的 Zod 集成工作,我们尽力使其对 TypeScript 的支持达到了令人满意的水平。
在决定了数据库和 ORM 后,我们启动了一个流程,最终导致整个应用程序重写数十个数据结构和数百个查询。
我们如何计划迁移?
代码重写即将结束之时,我们开始考虑如何进行迁移操作,将 MongoDB 数据映射到 PostgreSQL,同时将对 Infisical 云平台的干扰降至最低。
鉴于 Infisical 在客户基础设施中的关键作用,我们立即排除了绝对停机方案的可能性。我们不得不妥协的是,在短暂的迁移窗口期间禁止写入操作(即客户将无法创建或更新应用程序配置),以换取更高的数据完整性保证。这种权衡似乎可以接受,因为客户主要从 Infisical 获取机密信息,并且他们每秒更新其应用程序配置的可能性较少。
接下来,关于实际的迁移操作,我们需要从 MongoDB 转储数据、仔细转换,然后将其插入回 PostgreSQL。在审核迁移顺序时,我们克服了一些挑战,例如确保 NoSQL 中的各种树状结构正确转换为其关系对应结构;这对于具有递归考虑的文件夹等数据结构尤其敏感。
我们还发现需要一种持久的方式来存储 MongoDB 中的标识符并将其映射到 PostgreSQL 中的标识符;考虑到处理的数据量之庞大,在内存中这样操作是行不通的。最后,我们决定使用 LevelDB 键值存储来协助标识符存储和查找操作,将数据逐表移动到 PostgreSQL 中。
迁移
最后,我们准备好进行迁移。此时,没有直接参与代码库重写的人员用了一个季度的时间来改进 Infisical 的其他方面,包括进行前端更改、执行维护补丁、扩展客户端功能以及编写更好的文档。此时,所有人重新聚集起来,为迁移这一过程做准备——把应用程序代码库替换为新代码库,并将数据从 MongoDB 传输到 PostgreSQL。
作为准备工作的一部分,我们起草了一份详细的迁移清单以及预期时间表。
计划大体如下:
执行迁移的几周前,通过电子邮件和应用内横幅提前通知用户数据库即将进行升级。我们将对平台上的每个功能流程进行彻底测试,并对迁移进行试运行。
迁移过程预计六小时,期间平台仅允许读取操作。在此窗口期,我们将运行迁移脚本,将数据从 MongoDB 移动到 PostgreSQL,检查数据是否丢失,如果成功则将 DNS 切换到新实例。我们还有备用计划,以防意外。
迁移后,我们将解决残留问题,并推出使用 Infisical 和 PostgreSQL 的新文档。
准备好计划,我们就着手执行了。
迁移效果
幸运的是,迁移执行顺利,数据零丢失,仅有一些非必要功能出现故障;我们在随后的36 小时内解决了这些问题,将对客户的影响降到最低。
迁移后,我们观察到了许多好处:
平台性能显著提升,主要归功于连接查询优化。使用 MongoDB 时,平台常常通过低效的聚合查询和多次网络跳转来实现所需功能。例如,由于我们核心数据的关系特性,经常需要执行很多 $lookup 操作来模拟 SQL 中的连接;此类操作效率低下,同时通常需要相应地扩展数据库和应用程序实例。迁移到 PostgreSQL 后,我们避免了这些低效操作,也降低了50%的数据库支出。
目前平台采用了更好的数据验证方式,植根于数据库层而不是应用层。MongoDB 被设计为无模式,因此它依赖 Mongoose 的框架来定义数据类型、必需字段和验证规则。有了 PostgreSQL,我们就不会再面临以往 Mongoose 权限之外访问或修改数据库时会出现的数据不一致问题。
最后也是最重要的是,我们相信 Infisical 现在更易于自行托管,客户无需额外的配置开销就能够进行 POC,例如处理 MongoDB 中的副本集以启用事务功能。
总体而言,考虑到当前的目标、任务范围及执行效果,我们认为此次迁移非常成功。我们打算在今后获得更多数据后公布更具体的结果。
结论
从 MongoDB 迁移到 PostgreSQL 从开始就不是一个容易的决定。总而言之,我们花了 3 到 4 个月的时间来执行该计划,并围绕为什么这样做、如何做等问题进行仔细规划和讨论,然后小心翼翼地执行这一切。
对于阅读本文的任何人,我强烈建议在尝试这样一项大型工作之前,深入思考用例和实施方案。总之,我非常高兴一切都按计划进行,我们能够提供如此巨大的更新,这将为 Infisical 用户带来巨大的变化。
非常感谢 Akhil Mohan 对迁移工作的大力支持,以及Infisical 公司其他所有人对迁移过程的协助。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721