何时该离开 MongoDB/Cassandra/SQL?来自 Discord 的教训

2022年5月26日 7 min read

在系统设计中一个非常容易引起争议的问题是:应该使用哪个数据库?

SQL、MongoDB、Cassandra、ScyllaDB、Redis、Elasticsearch……每种数据库都有人称赞。但 Discord 的案例展示了一个更现实的教训:数据库不是一次选择就能永远正确的。

Discord 曾经从 MongoDB 迁移到 Cassandra,后来又将消息存储迁移到 ScyllaDB。不是因为旧数据库“糟糕”,而是因为运营要求、规模 (scale) 和访问模式 (access pattern) 发生了变化。

参考资料

1. 不要凭感觉选择数据库

一种非常危险的数据库选择方式是:

MongoDB 易于使用 → 选择 MongoDB
SQL 很熟悉 → 选择 PostgreSQL
Cassandra 扩展性好 → 选择 Cassandra
Redis 速度快 → 把所有东西都放进 Redis

更正确的方法是问:

  • 数据具有很强的关系性吗?
  • 主要查询 (query) 是什么?
  • 是读多还是写多?
  • 需要事务 (transaction) 吗?
  • 需要全文搜索 (full-text search) 吗?
  • 数据朝哪个方向增长?
  • 系统能容忍最终一致性 (eventual consistency) 吗?
  • 团队有能力运营它吗?

数据库不仅仅是存储数据的地方。它还带来了一套运营模式 (operational model)。

2. 对于大多数产品,SQL 仍然是很好的起点

对于 CRM、仪表板或小型 SaaS,PostgreSQL/MySQL 仍然是一个合理的起点。

因为它们有:

  • 事务 (transactions);
  • 清晰的 schema;
  • 良好的连接 (joins);
  • 强大的索引 (indexes);
  • 灵活的查询;
  • 易于备份;
  • 容易找到懂用的人;
  • 良好的生态系统。

例如,CRM 有线索 (leads)、联系人 (contacts)、交易 (deals)、报价 (quotes)。这天然就是关系型数据:

客户 ── 交易 ── 报价

    └── 活动

使用 SQL 有助于清晰地建模,并避免数据过早变得松散。

3. MongoDB 适合文档数据快速变化的情况

MongoDB 适合某些情况:

  • 灵活的文档数据;
  • schema 快速变化;
  • 对象嵌套层级深;
  • 不需要复杂的连接 (joins);
  • 原型设计 (prototyping) 速度很重要。

例如,存储工作流配置:

{
  "workflow_id": "wf_123",
  "steps": [
    { "type": "trigger", "event": "lead.created" },
    { "type": "condition", "field": "source", "equals": "website" },
    { "type": "action", "name": "send_follow_up" }
  ]
}

对于这种格式,文档数据库可能很方便。

但是,如果你开始需要复杂的报告、连接多个表或严格的数据约束,SQL 会更容易让人活下去。

4. Cassandra/ScyllaDB 解决的是完全不同的问题

Cassandra 和 ScyllaDB 并不是为了在所有方面取代 SQL。它们在大型分布式工作负载、基于明确键的读/写、水平扩展和良好的容错性方面表现出色。

但权衡 (trade-offs) 是:

  • 查询必须围绕访问模式进行设计;
  • 你不能像在 SQL 中那样随意查询;
  • 数据建模更难;
  • 运营复杂性更高;
  • 迁移和一致性需要深入的了解。

Discord 使用 Cassandra 是因为消息存储需要海量扩展和容错能力。但当 Cassandra 集群造成太多繁重工作 (toil)、不可预测的延迟、GC 问题、压缩痛苦 (compaction pain) 和热分区 (hot partitions) 时,他们迁移到了 ScyllaDB 并添加了数据服务以更好地控制流量。

教训:Cassandra/ScyllaDB 解决了一组问题,但也带来了另一组问题。

5. 什么时候应该考虑迁移?

不要仅仅因为“听说新技术更好”就进行迁移。

当出现明确信号时,你应该考虑迁移:

延迟不再稳定

p99/p999 增加,用户觉得慢,值班 (on-call) 开始变得疲惫。

维护成本增加

团队花费了太多时间在救火上,而不是构建功能。

访问模式不适合当前数据库

例如,你需要快速查询时间线 (timeline),但当前的 schema 使该查询过于昂贵。

规模超出了最初的设计

不仅仅是 2 倍的增长,而是数据/流量的基本性质发生了变化。

新数据库解决了正确的痛点

不是“新数据库更快”,而是“它对于我们的工作负载更快”。

6. 安全迁移需要清晰的计划

最少的数据迁移应该包括:

1. 了解当前的工作负载
2. 在接近生产的数据上进行基准测试 (Benchmark)
3. 如果需要,进行双写 (Dual-write) 或影子写入 (shadow-write)
4. 回填 (Backfill) 旧数据
5. 验证新旧数据库之间的读取结果
6. 通过功能标志 (feature flag) 进行切换 (Cutover)
7. 制定回滚 (rollback) 计划
8. 监控 p50/p95/p99、错误率、不匹配率

Discord 通过比较两个数据库之间一部分的读取结果来进行自动化验证。这是一个非常值得学习的细节。迁移不仅仅是移动数据,它还要证明数据在移动后仍然正确。

结论

从 Discord 身上学到的教训不是“去用 ScyllaDB”。教训是:你的数据库必须遵循你的访问模式和运营现实。

在规模很小的时候,选择易于理解、运营和调试的数据库。当你成长时,观察真正的瓶颈。如果你必须迁移,那是因为数据和工作负载迫使你这么做,而不是因为新技术看起来更酷。

New posts, shipping stories, and nerdy links straight to your inbox.

2x per month, pure signal, zero fluff.


分享这篇文章: