基准测试:Postgres vs.MongoDB
他的咖啡杯停在半空中,嘴都张开了。仪表板上十四个微服务变黄了。Slack 炸了。我只是坐在那里微笑,因为我花了三周时间证明这是正确的决定。
关于 MongoDB,没人告诉你这件事:我们在 2019 年选择它,是因为一位会议演讲者说"无模式是未来"。那位演讲者现在在一家 Postgres 公司工作。我没有编造这个。
五年来,我们运行了两个数据库。Postgres 用于"严肃的"关系型数据。Mongo 用于事件、日志,以及任何我们称之为"非结构化"的数据,因为我们太懒了,懒得设计合适的模式。两个连接池在消耗内存。两个备份计划需要维护。每个季度都会把我们吵醒的两个独立事件。
然后一位初级开发者问了一个让我崩溃的问题:"我们需要两个数据库吗?"
我张嘴想要解释。什么也没说出来。我没有答案。五年的架构决策,我无法为其中任何一个辩护。
所以我做了任何工程师在假设崩溃时都会做的事情。我编写基准测试来证明自己是对的。
基准测试证明我错了。而且非常残酷。
1、改变一切的那次测试
两个数据库使用相同的硬件。相同的数据集。1000 万个事件文档,看起来像这样:
{
"user_id": "u_8x7k2m",
"event": "page_view",
"metadata": {
"url": "/products/widget",
"device": "mobile",
"referrer": "google.com"
},
"tags": ["organic", "returning"]
}
嵌套对象。数组。动态字段。所有 MongoDB 据称为此设计的东西。
我运行了我们在生产环境中实际使用的查询。从我们的应用程序日志中提取的真实查询。
按用户 ID 简单查找:
┌──────────────────┬────────────┬────────────┐
│ Database │ Avg (ms) │ P99 (ms) │
├──────────────────┼────────────┼────────────┤
│ Postgres JSONB │ 0.8 │ 2.1 │
│ MongoDB │ 1.2 │ 4.7 │
└──────────────────┴────────────┴────────────┘
Postgres 更快。在文档查询上。对抗一个专门为文档构建的数据库。
我又运行了一次。同样的结果。我调整了 MongoDB 的 WiredTiger 缓存。我阅读了 2017 年的论坛帖子。我乞求数字证明我是对的。
它们拒绝了。
嵌套字段查询:
┌──────────────────┬────────────┬────────────┐
│ Database │ Avg (ms) │ P99 (ms) │
├──────────────────┼────────────┼────────────┤
│ Postgres JSONB │ 12.4 │ 28.3 │
│ MongoDB │ 14.1 │ 31.8 │
└──────────────────┴────────────┴────────────┘
仍然是 Postgres。差距缩小了但从未反转。
数组包含操作:
┌──────────────────┬────────────┬────────────┐
│ Database │ Avg (ms) │ P99 (ms) │
├──────────────────┼────────────┼────────────┤
│ Postgres JSONB │ 18.7 │ 42.1 │
│ MongoDB │ 16.2 │ 38.4 │
└──────────────────┴────────────┴────────────┘
Mongo 终于赢了一次。领先 2.5 毫秒。在数组操作上。它存在的全部理由。
聚合管道与原始 SQL:
┌──────────────────┬────────────┐
│ Database │ Time (s) │
├──────────────────┼────────────┤
│ Postgres JSONB │ 3.2 │
│ MongoDB │ 4.8 │
└──────────────────┴────────────┘
甚至都不接近。SQL 在分析查询上彻底摧毁了聚合框架。
2、真正令人痛苦的部分
基准测试很尴尬。但真正的痛苦来自于我检查我们的事件模式历史时。
我们选择 MongoDB 是为了"灵活性"。无需迁移的模式更改。可以自由演化的文档。
我们的事件模式两年内没有改变过。一次都没有。我们一直在为从未实际使用过的灵活性支付运营复杂性税收。
每个文档都有完全相同的结构。我们整个时间都可以使用带有类型化列的常规 Postgres 表。
我围绕一个从未到来的假设未来大规模配置了整个基础设施。五年的不必要复杂性,因为我相信一个会议演讲胜过相信自己的测量。
3、现在的架构
┌─────────────────────┐
│ Application │
└──────────┬──────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Relational │ │ JSONB │ │ Full-Text │
│ Tables │ │ Events │ │ Search │
└─────────────┘ └─────────────┘ └─────────────┘
│
┌──────────┴──────────┐
│ PostgreSQL │
└─────────────────────┘
一个数据库。一个备份策略。一个连接池。在事故期间只需调查一件事而不是两件事。
迁移脚本几乎简单得令人侮辱:
cursor = mongo_db.events.find().batch_size(10000)
batch = []
for doc in cursor:
batch.append(doc)
if len(batch) >= 10000:
pg_cur.executemany(
"INSERT INTO events (data) VALUES (%s)",
[(Json(d),) for d in batch]
)
batch = []
pg_conn.commit()
四个小时移动 1000 万个文档。基础设施成本下降了 40%。数据库事故下降到零。新工程师不再问为什么我们需要两个数据库,因为我们不再需要了。
4、不舒适的真相
MongoDB 不是糟糕的数据库。它是一个大多数团队实际上并不需要的专门工具。
如果你在 50 台机器上进行分片,拥有结构差异巨大的真正多态文档,MongoDB 是有意义的。如果你有一个深入了解 Mongo 的团队,并且你的文档确实彼此不同,继续使用它。
但如果你在运行一个副本集,拥有数百万个看起来完全相同的文档,你正在为永远不会使用的复杂性付费。
那个提出了不适问题的初级开发者?她现在领导着我们的数据平台。
那位在我删除集群时咖啡变凉的 CTO?他在两个月后批准了我的晋升。
有时最资深的技术决策是承认多年前你做出了错误的决定。检查你的假设。运行基准测试。
工程最难的部分不是构建系统。而是承认你构建了错误的系统。
原文链接: JSONB Is Faster Than You Think: We Benchmarked Postgres vs. Mongo
汇智网翻译整理,转载请标明出处