[博客翻译]从MongoDB到PostgreSQL的大迁移


原文地址:https://infisical.com/blog/postgresql-migration-technical


过去一年,Infisical发展迅速,每天处理超过5000万份秘密数据,将应用配置和秘密信息发送给需要它们的团队、CI/CD管道和服务器应用。随着使用量持续增长,我们不得不不断升级我们的架构。最近,Infisical完成了一次全面的从MongoDB到PostgreSQL的数据库迁移。这个过程包括深思熟虑该举措、采用新技术、创建新的数据库模式、重构逻辑、重写查询,并迁移数百万(甚至数十亿)数据库记录到PostgreSQL。这是一个复杂的过程,但无疑是必要的,对平台的提升大有裨益。

postgresql-migration.webp

这是我们在决定从MongoDB转向PostgreSQL背后的原因,以及我们如何操作的。希望这个故事能引起共鸣,对那些可能有一天考虑类似数据库迁移的人有所启发。我们从何开始当我们最初构建Infisical时,我们选择了团队最熟悉的堆栈,其中包括MongoDB + Mongoose ORM,因为这种组合提供了最少的开销,让我们能快速发布高质量功能。正如Tony Hoare爵士所说,“过度优化是万恶之源”,当时我们并没有进一步优化的必要。

当时,我们更专注于构建Infisical Cloud(即管理的SaaS服务),因此我们没有预料到会有许多用户选择自托管产品,所以它并未充分考虑到这种用例。为什么选择MongoDB在早期阶段,MongoDB对Infisical表现良好,但随着产品应用场景从管理服务扩展,它开始显示出局限性。随着时间的推移,我们发现许多组织,特别是那些在合规性和安全性交汇点运营的组织,更倾向于自托管Infisical,而不是使用Infisical Cloud;其他人有本地部署要求。

随着对自托管Infisical需求的增长,我们发现自己需要添加许多功能来降低用户自托管的门槛,这促使我们最终放弃MongoDB,转而选择PostgreSQL。在实践中,我们和客户经常遇到MongoDB在能力与易用性方面的限制,比如不支持事务、清理操作、云提供商管理服务中版本不一致的问题,更不用说无模式数据库设计结构带来的问题。我在下面详细讨论了其中一些挑战:配置数据库事务的困难:在MongoDB中设置事务并不简单,因为它要求在集群模式下运行,并且需要额外的配置开销。这使得在生产环境中运行简单的Infisical原型变得极其困难,对于处理高度敏感数据、数据完整至关重要的产品来说,这是不可接受的。

失去了关系型特性:使用MongoDB,我们失去了关系型世界中的许多特性,如CASCADE(当指定时,在目标资源删除时会自动删除其他表中所有相关的资源)。这尤其痛苦,因为我们的数据本质上是关系型的。因此,我们在旧代码库中使用了大量笨重的删除函数,但它们并不能完全解决问题,反而会在MongoDB数据库中留下悬而未决的资源。

云提供商支持不足:在MongoDB的许可证变更后,许多云提供商选择提供旧版本的MongoDB。这使得确保Infisical在除最新稳定版本之外的其他版本上的功能可用变得困难。

缺乏MongoDB经验:由于更多人熟悉部署基于SQL的数据库,他们在扩展和正确配置MongoDB时遇到困难,这导致我们为支持MongoDB的客户提供的帮助不成比例增加。

在十几个原因中,我们意识到全面转向更通用的数据库是让Infisical对全球团队和组织更易访问的关键特性。为什么选择PostgreSQL在寻找新数据库时,我们首先列出了对我们至关重要的因素:管理的便利性(包括配置、部署和扩展),内置的事务支持,以及关系型能力。在权衡过程中,我们还考虑了是否应该构建自己的集成存储解决方案,还是寻求外部存储方案。

集成存储:我们曾考虑将SQLite这样的数据库系统直接打包到Infisical中,并采用水平复制策略来减少延迟,避免额外的网络跳数。在这个模型中,扩展系统意味着部署多个Infisical实例,并通过像Raft这样的共识算法进行通信。虽然这个方案看起来很好,因为用户无需连接任何依赖即可运行Infisical,但实现这个愿景的工具生态系统感觉还不够成熟,而且所需的工程工作量巨大。

外部存储:我们可以简单地用PostgreSQL或MySQL等其他数据库替换MongoDB,并利用其内置的扩展能力。虽然这个解决方案并没有完全消除使用Infisical时需要外部依赖的问题,但通过不选择MongoDB,我们已经获得了显著的好处。在支持一个或多个数据库的选择中,我们认为支持多个意味着会错过每个解决方案的独特优势,也会增加工程开销。

经过深思熟虑后,我们选择了PostgreSQL。除了拥有活跃的社区、详尽的文档和众多解决方案和扩展外,我们尤其欣赏其开源性质,大多数云提供商都提供了PostgreSQL的管理服务。最重要的是,这意味着Infisical的用户可以更轻松地在任何云提供商上自托管我们的平台,并与对应的PostgreSQL管理服务配合使用。此外,由于PostgreSQL的广泛采用,我们有信心用户在使用Infisical时会遇到更少的困难。

ORM的问题确定了PostgreSQL后,我们需要考虑应用如何与数据库交互。一开始,我们希望找到一个与使用MongoDB和Mongoose ORM相似的解决方案。因此,我们根据成熟度、可视化支持和适当程度的抽象化评估了Drizzle ORM、Prisma ORM、TypeORM和Knex.js(查询构建器)等候选者。

11.png

最终,我们选择使用Knex.js查询构建器,而不是ORM,以更好地控制数据库。虽然承认直接使用SQL是最灵活的,但考虑到没有抽象层的潜在错误风险和维护复杂性,我们觉得这种方法过于冒险且冗余。此外,除了接近基础的SQL之外,Knex.js还提供了自己的数据填充和迁移工具包,拥有成熟的生态系统、优秀的文档和几乎可以解答任何查询的答案。结合一些定制的Zod集成工作,我们将其调整到了支持TypeScript的满意水平。

确定了数据库和ORM后,我们开始了重写数十个数据结构和数百个查询的过程。

如何规划迁移在代码重写接近尾声时,我们开始考虑如何在最小程度地影响Infisical Cloud平台的情况下进行迁移操作,将MongoDB数据映射到PostgreSQL。

鉴于Infisical在客户基础设施中的关键作用,我们立即排除了任何可能导致绝对停机的可能。我们必须妥协的是,在短暂的迁移窗口(即用户无法创建或更新应用配置)中禁止写操作,以换取更高的数据完整性保证。考虑到用户主要从Infisical获取秘密信息,而更新应用配置的频率较低,这个权衡是可接受的。

关于实际迁移操作,我们需要从MongoDB中导出数据、仔细转换并插入到PostgreSQL中。在审计迁移流程时,我们面临了挑战,如确保NoSQL中的树状结构正确转换为关系型等价物,特别是对于具有递归考虑的文件夹数据结构。我们还发现需要持久地存储和映射MongoDB中的标识符到PostgreSQL中的标识符;考虑到处理的数据量,内存中的方法不可行。最终,我们选择使用LevelDB键值存储来协助标识符存储和查找操作。我们将数据逐表迁移到PostgreSQL。

伟大的迁移终于,我们准备进行迁移。在这个阶段,没有直接参与代码重构的人员花了宝贵的一个季度改进Infisical的其他方面,包括前端更改、打补丁、扩展客户端功能和编写更好的文档。现在我们聚在一起准备迁移本身,即将应用代码库替换为新代码,并从MongoDB迁移到PostgreSQL。

在准备过程中,我们制定了详细的迁移检查列表和预期时间表。概括来说,计划如下:

  • 在迁移前几周,我们通过电子邮件和应用内通知提前告知用户即将进行的数据库升级。我们将对平台上的每个功能流程进行彻底测试,并进行迁移的预演。
  • 迁移本身将在六小时的窗口内进行,期间平台只允许读取操作。在此期间,我们将运行迁移脚本将数据从MongoDB迁移到PostgreSQL,检查数据丢失,并在成功后切换DNS到新实例。当然,我们也有备份计划以防万一。
  • 迁移完成后,我们将解决任何遗留问题,并开始推出与Infisical和PostgreSQL一起工作的新文档。迁移结果幸运的是,迁移执行顺利,没有数据丢失,只有少数非关键功能故障。我们在接下来的36小时内解决了这些问题,对客户的影响最小。

迁移后,我们观察到了许多好处:平台性能显著提升,主要归功于JOIN查询优化。在MongoDB中,平台经常使用效率低下的聚合查询和多次网络请求来实现功能。由于核心数据的关联特性,我们经常需要执行多个$lookup操作来模拟SQL中的JOIN;这些操作效率低下,并且通常需要相应地扩展数据库和应用实例。转向PostgreSQL后,我们避免了这些低效操作,这还导致我们数据库账单减少了50%。

平台现在在数据库级别进行了更好的数据验证,而不是在应用层。由于MongoDB设计为无模式的,它依赖Mongoose框架来定义数据类型、必填字段和验证规则。有了PostgreSQL,我们不再面临数据不一致的问题,这些不一致以前可能在Mongoose的控制范围之外出现。

最后但同样重要的是,我们相信Infisical现在更容易自托管了,用户可以进行原型开发,无需额外的配置开销,如在MongoDB中处理副本集以启用事务能力。

总的来说,考虑到目标、任务的范围和执行结果,我们认为这个举措非常成功。我们计划在未来获得更多数据后发布更具体的成果。

结论从MongoDB转向PostgreSQL的决定并非易事。整个过程耗时3-4个月,我们仔细计划了为什么要进行迁移、如何进行,并且在执行过程中非常谨慎。对于阅读这篇文章的每个人,我强烈建议在尝试这样大规模的项目之前深入思考用例和实施方式。总的来说,我很高兴一切按计划进行,我们能够实现如此大的更新,对Infisical的用户将产生重大影响。