MongoDB 到 Postgres
注意:这篇博文整合了我的 MongoDB 到 Postgres (2024-03-06) 和 Sequelize 与 Prisma (2023-05-25)。原始博客已被移除,这篇博客取而代之,因为二者本质上包含相同的内容/信息。迁移始于 2023 年 3 月初,切换发生在 2023 年 11 月中旬,而旧 MongoDB 系统的所有实例都已于 2024 年 1 月初 完全关闭。
介绍
在 eBay 工作期间,我遇到了职业生涯中技术上最具挑战性的问题:将存储管理系统(STMS)从 MongoDB 迁移到 Postgres。这不仅仅是一次简单的数据库替换;它是对一个关键系统进行的完整架构转型,该系统每分钟会在 eBay 的数据中心摄取超过 150 万条指标,而且要求零停机并保持几乎所有现有功能。
什么是 STMS?
存储管理系统(STMS)是 eBay 的服务与存储基础设施(SSI)团队使用的一项关键内部工具。它监控并管理遍布 eBay 数据中心的设备,使工程师能够:
- 监控来自数十个阵列、交换机、主机、磁盘组和集群的指标
- 处理交换机和阵列的告警
- 完成诸如主机分配之类的高级任务
- 访问供其他 eBay 内部服务使用的实时数据
STMS 覆盖 eBay 3 个数据中心中的 70 多个阵列、60 台交换机、1100 台主机、900 个磁盘组和 200 个集群。鉴于它在 eBay 基础设施中的关键作用,任何停机或功能丧失都会直接影响公司的核心服务和业务运营。
挑战
为什么需要迁移
从 MongoDB 迁移到 Postgres 的决定并不是轻率做出的。虽然 MongoDB 最初为 STMS 提供了良好支持,但随着我们数据关系复杂性的增长,以及对更高级查询能力的需求,Postgres 成为了我们用例更好的长期解决方案。
这个问题为什么困难
这次迁移的复杂性源于若干根本性挑战:
1. 根本性的数据库差异 MongoDB 和 Postgres 本质上是不同的数据库。MongoDB 是文档型数据库(NoSQL),这意味着数据以 JSON 形式存储在集合中,就像文件柜里的文档。Postgres 是关系型数据库(SQL),这意味着数据以表中的行形式存储,就像电子表格一样。
2. 代码库架构 STMS 的整个后端都是以处理和管理 JSON 数据为目标构建的,并且数据库操作只使用与 MongoDB 专门兼容的包。这意味着不仅要更换数据库,还要重构整个应用处理数据的方式。
3. 零停机要求 由于 STMS 作为内部工具的重要性,在迁移期间不能有任何停机。整个过程中,系统必须持续服务每分钟 150 万以上的指标。
4. 紧迫的时间表与有限的经验 迁移必须在几个月内完成,且最初没有明确的执行计划。我和我的同事都没有将大型遗留代码库从 NoSQL 迁移到 SQL 数据库的经验,而且我之前对 Postgres 的经验也有限。
5. 规模与复杂性 这次迁移涉及将 36 个 MongoDB 集合转换为 74 个 Postgres 表,需要仔细考虑关系、索引和查询优化。
选择合适的 ORM:Sequelize 与 Prisma
最初的重大决策之一是选择一个 ORM(对象关系映射)工具。由于我们的代码库本来就设计为使用 Mongoose 来操作 MongoDB,因此使用 ORM 将提供最平滑的过渡路径。
需求分析
经过对项目需求的仔细分析,我为任何 ORM 解决方案确定了基本标准:
- 必须是一个 JavaScript 包(我们的大部分代码都是用 JavaScript 编写的)
- 必须支持 Postgres 及其大部分功能
- 性能必须至少与 Mongoose 持平,或者更好
- 必须是开源且有人维护的
候选方案
经过大量研究,我把范围缩小到两个主要竞争者:Sequelize 和 Prisma。我使用 Docker 为 Postgres 创建了全面的测试环境,并将我们最大、最复杂的数据集从文档结构转换为表结构。
测试方法
对于每个 ORM,我在关键操作上测量性能:
- 创建一条记录所需的时间
- 更新一条记录所需的时间
- 更新嵌套记录所需的时间(关系和 JSON 键值)
- 删除一条记录所需的时间
- 查询/获取一条记录所需的时间
决定:Sequelize
大约在 2023 年 5 月 15 日,我决定 Sequelize 更适合作为我们的 ORM。原因如下:
Sequelize 的优势:
- 真正开源,而且不是由一家有资金支持的初创公司维护
- 支持 Postgres 的大部分功能
- 性能更好,尤其是与 Prisma 相比
- 拥有超过 10 年开发历史的成熟生态系统
- 使用 JavaScript 类进行灵活的模型/模式表示
- 支持复杂的连接和过滤选项,包括正则表达式
性能结果:
在我的测试中,Sequelize 明显优于 Prisma。对于我们的大型数据集记录:
- Sequelize:每条记录约 2.26 秒
- Prisma:每条记录约 11.21 秒
对于我们的用例,Prisma 的速度大约比 Sequelize 慢 5 倍。此外,从我们最大的数据集中删除一条记录,Prisma 竟然花了将近 4 分钟,这对我们的需求来说是不可接受的。
Sequelize 的挑战:
- 模型表示更复杂、更臃肿(564 行 vs Mongoose 的 262 行)
- 在某些情况下语法令人困惑
- 数据库迁移的复杂性
- 与 Prisma 相比,文档不够全面
Sequelize 与 Prisma 的优缺点比较
为了更全面地说明我为什么选择 Sequelize,我想分享我在评估过程中为这两个 ORM 汇总的详细优缺点。我还查看了截至 2023 年 5 月 15 日它们在模式表示和社区支持方面的表现。这种更深入的分析帮助我坚定了选择,我也希望它对任何面临类似决定的人都有帮助。
Sequelize 的优点:
- 有一个 sync() 函数,可以自动为你创建和处理表,节省大量手工工作。
- 能处理嵌套数据的复杂连接,这对 STMS 的结构至关重要。
- 支持广泛的过滤选项,包括正则表达式,为查询提供灵活性。
- 模型/模式表示使用原生 JavaScript 中的类来完成,这些类高度可定制,能适应特定需求。
- 无缝处理数据库连接,包括支持多个读连接。
- 支持原始 SQL 查询,方便你在需要时深入底层。
- 截至 2023 年 5 月 15 日的社区数据:在 NPM 上,最近 14 天前更新,每周下载量 1,505,835 次;在 GitHub 上,昨天更新,有 4.2k 个 Fork 和 27.9k 个 Star。它已使用 MIT 许可证开源超过 10 年,所以我相信它还会继续如此。
Sequelize 的缺点:
- 模型/模式表示可能变得非常复杂和臃肿。例如,我们大型数据集的 Mongoose 表示大约是 262 行(包括空行),而同一数据集在 Sequelize 中膨胀到 564 行。
- 在某些场景下,语法会令人困惑且复杂,这有时拖慢了我的进度。
- 迁移或编辑数据库很麻烦。即使有 sequelize-cli 生成迁移脚本,仍然相当笨重,不过我注意到这也是大多数 ORM 的常见痛点。
- 文档不算好,尽管它正在改进。幸运的是,像 ChatGPT 这样的工具由于 Sequelize 历史悠久,对它有相当扎实的理解,这帮助弥补了缺口。
- 没有 Prisma 那么敏感于类型,这在某些项目中可能导致问题。
- TypeScript 支持有限,虽然这对 STMS 不是问题,但对其他人来说可能是决定性因素。
Prisma 的优点:
- 使用自己的模式语言,使模型创建更简洁、更精炼。作为比较,我们的大型数据集在 Mongoose 中用了 262 行,而 Prisma 只用了 221 行。
- 自带一个 CLI 工具,简化数据库创建和迁移,这是我目前见过 ORM 中最好的,尽管并不完美。
- 支持原始 SQL 查询,在需要时提供灵活性。
- 与 Sequelize 相比,代码语法更简洁、更容易理解,因此更容易上手。
- 通过其客户端自动为 Node.js 和 TypeScript 生成查询构建器,这一点很不错。
- 拥有出色而清晰的文档。ChatGPT 对 Prisma 的更新没那么及时,但官方文档往往弥补了这一点。
- 截至 2023 年 5 月 15 日的社区数据:在 NPM 上,最近 6 天前更新,每周下载量 1,344,705 次;在 GitHub 上,3 小时前更新,有 1.1k 个 Fork 和 31.3k 个 Star。
Prisma 的缺点:
- 不支持 Postgres 的正则表达式过滤,不过它提供了诸如 “contains”、“includes” 和 “startsWith” 之类的替代方案。
- 在我的测试中,性能是一个大问题。为我们的大型数据集创建记录时,Prisma 每条记录大约花 11.21 秒,而 Sequelize 只需 2.26 秒,约慢 5 倍。
- 从大型数据集中删除单条记录几乎花了 4 分钟,这对我们的需求来说是致命缺陷。
- 即使在一个复杂的、三层深的关系数据集上做了公平比较,Sequelize 在删除操作上仍然显著更快。
- Prisma 由一家获得 5650 万美元融资的初创公司支持。虽然其主要 ORM 代码在 Apache-2.0 许可证下开源,但我对未来潜在的许可证变更保持警惕,就像 MongoDB 曾经发生的那样。
这些详细的比较清楚地表明 Sequelize 更符合 STMS 的需求,尤其是在性能和长期可靠性方面。不过我觉得像这样把它拆解开来,也许能帮助其他在项目中面临同样选择的人。
迁移过程
数据结构转换
将 MongoDB 的文档结构转换为 Postgres 的关系结构需要周密的规划。我必须:
- 分析关系: 识别 MongoDB 文档之间如何相互关联,并设计合适的外键关系
- 规范化数据: 将嵌套文档在适当的情况下拆分为独立的表
- 保留 JSON 特性: 对于真正非结构化且需要保持灵活的数据使用 JSONB 列
- 设计索引: 创建合适的索引以提升查询性能
自定义解决方案
这次迁移需要开发若干自定义解决方案:
1. 数据迁移脚本 我创建了全面的脚本来:
- 从 MongoDB 集合中提取数据
- 将文档结构转换为关系格式
- 将数据导入具有正确关系的 Postgres 表中
2. API 兼容层 为了保持零停机时间,我构建了一个兼容层,它可以:
- 根据迁移状态将请求路由到 MongoDB 或 Postgres
- 在过渡期间确保数据一致性
- 提供回退机制
3. 自定义中间件 开发了中间件来处理 MongoDB 和 Postgres 在某些操作上的差异,确保现有 API 端点无需修改即可继续工作。
克服技术挑战
处理复杂关系
最大的挑战之一是将 MongoDB 的嵌入式文档转换为 Postgres 关系。例如,单个 MongoDB 文档可能包含:
- 基本属性
- 表示相关实体的嵌套对象
- 嵌入式文档数组
这必须被仔细分解为:
- 主要实体的主表
- 用于多对多关系的连接表
- 用于一对多关联的外键关系
查询优化
MongoDB 的查询模式不能直接转换为 SQL。我必须:
- 将复杂的聚合管道重写为 SQL 连接
- 为新的查询模式优化索引
- 确保查询性能达到或超过 MongoDB 的性能
数据完整性
确保迁移期间的数据完整性需要:
- 全面的验证脚本
- 回滚程序
- 过渡期间的实时数据同步
结果与影响
STMS 从 MongoDB 到 Postgres 的迁移已成功完成,在保持几乎所有功能和特性的同时实现了零停机时间。结果超出了预期:
性能提升:
- 复杂关系型查询的查询性能得到提升
- 更好的数据一致性和完整性
- 更高效的存储利用率
运营收益:
- 增强的监控和调试能力
- 与 eBay 现有基于 SQL 的工具更好地集成
- 改进的备份和恢复流程
团队影响:
- 增强团队对关系型数据库的知识
- 为未来的数据库迁移建立了模式
- 创建了可复用的工具和流程
获得的技术技能
这个项目显著扩展了我的技术专长:
数据库技术:
- 深入理解 Postgres 功能和优化
- SQL 查询优化和性能调优
- 数据库设计模式和规范化
- 主从待机数据库配置
开发工具:
- Sequelize ORM 和查询构建
- 数据库迁移策略
- 性能测试方法
- 数据验证和完整性检查
架构模式:
- 零停机迁移策略
- API 兼容层
- 数据库抽象模式
- 监控和告警系统
个人与职业成长
这个迁移项目对我的职业发展具有变革性意义。它把我推向了未知领域,要求我具备:
领导能力:
- 在没有先前经验的情况下领导一个复杂的技术项目
- 在压力下做出关键的架构决策
- 与多个团队和利益相关者协调
解决问题能力:
- 将复杂问题分解为可管理的组件
- 为前所未有的挑战开发创造性的解决方案
- 平衡多个相互竞争的需求和约束
沟通与团队合作:
- 向非技术利益相关者解释技术概念
- 为将来参考记录流程和决策
- 指导团队成员使用新技术和新模式
经验教训
技术经验
- 数据库选择很重要: 在 NoSQL 和 SQL 之间的选择应基于具体用例和长期需求
- 性能测试至关重要: 理论上的优势并不总能转化为实际的性能提升
- 迁移规划: 对于复杂迁移来说,全面的规划和测试是必不可少的
- 工具投入: 预先构建合适的工具可以节省大量时间并减少错误
项目管理经验
- 利益相关者沟通: 定期更新和清晰沟通可防止误解
- 风险管理: 备有回退计划和回滚程序至关重要
- 时间线管理: 为意外挑战和学习曲线预留缓冲时间
- 文档: 详尽的文档能够促进知识传递和未来维护
结论
STMS 从 MongoDB 到 Postgres 的迁移是我职业生涯中解决过的最具挑战性且最有收获的技术问题。它不仅需要技术专长,还需要领导力、规划能力和适应能力。该项目的成功表明,只要有周密的规划、彻底的测试和对卓越的坚持,即使是最复杂的技术挑战也能被克服。
这段经历从根本上改变了我对软件工程的看法,强调了以下重要性:
- 在做出技术决策之前理解完整的上下文和需求
- 投入时间用于合适的工具和测试
- 在复杂项目中保持清晰沟通
- 在必要时愿意学习新技术和新方法
这次迁移的成功不仅提升了 STMS 的能力,还建立了持续为 eBay 基础设施项目带来收益的模式和流程。它强化了我的信念:通过未知挑战并取得成功,是个人和职业发展的关键。
回顾来看,这个项目是我职业生涯中的一个转折点,使我从一个实现解决方案的开发者,成长为一个能够设计架构并领导复杂技术 উদ্যোগ的工程师。这段经历带来的信心和技能至今仍在指导我应对软件工程中的新挑战和新机会。