过去一年,Infisical发展迅速,每天处理超过5000万份秘密数据,将应用配置和秘密信息发送给需要它们的团队、CI/CD管道和服务器应用。随着使用量持续增长,我们不得不不断升级我们的架构。最近,Infisical完成了一次全面的从MongoDB到PostgreSQL的数据库迁移。这个过程包括深思熟虑该举措、采用新技术、创建新的数据库模式、重构逻辑、重写查询,并迁移数百万(甚至数十亿)数据库记录到PostgreSQL。这是一个复杂的过程,但无疑是必要的,对平台的提升大有裨益。
这是我们在决定从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(查询构建器)等候选者。
最终,我们选择使用Knex.js查询构建器,而不是ORM,以更好地控制数据库。虽然承认直接使用SQL是最灵活的,但考虑到没有抽象层的潜在错误风险和维护复杂性,我们觉得这种方法过于冒险且冗余。此外,除了接近基