Cassandra 的问题:为什么它不适合作为对象存储的元数据数据库

The Trouble With Cassandra: Why It's a Poor Choice For a Metadata Database for Object Stores

Cassandra 是一个流行的、经验证的 NoSQL 数据库,它支持键值宽列表。像任何强大的工具一样,Cassandra 也有其理想的用例——特别是,Cassandra 在支持写密集型工作负载方面表现出色,而在支持读密集型工作负载方面存在局限性。Cassandra 的最终一致性模型和缺乏事务、多表支持(如联接、子查询)也会限制其用处。

但是,将 Cassandra 用作对象存储系统的元数据数据库会引入巨大的复杂性,导致数据完整性和性能问题在规模上出现,尤其是当您想要将对象存储用作主要存储系统时。对象存储的需求远比 Cassandra 所构建的要简单和不同。

由于没有正确理解使用 Cassandra 作为对象存储元数据数据库的影响,许多对象存储供应商将其作为其架构的基础部分——不幸的是,这阻止了他们从简单的归档工作负载转向定义对象存储未来的现代工作负载(AI/ML、分析、Web/移动应用程序)。

让我们更详细地探讨一下为什么。

  1. Cassandra 从未设计用于管理文件或对象存储元数据,并且在这方面它预料之中地很弱。它不符合 ACID 规范。它没有足够的严格性来防止部分成功的写入、重复数据、矛盾等问题。Cassandra 不支持联接或外键,因此不会在 ACID 意义上提供一致性。此外,在发生故障的情况下,没有回滚事务的能力。

    虽然 Cassandra 在行级支持原子性和隔离,但它以高可用性和快速写入性能为代价牺牲了事务隔离和原子性。

  2. Cassandra 在 CAP 中被归类为 AP 系统。这意味着它以可用性和分区容忍性为代价牺牲了一致性。当将 Cassandra 用作对象存储的元数据数据库时,您要么可以快速,要么可以一致,但不能同时两者兼得。

    Cassandra 的可调整一致性是一种折衷,而不是一种特性。任何与QUORUM 或 ALL不同的设置意味着您有读取陈旧数据的风险。在执行对象数据操作之外,对读取和写入操作都应用此一致性设置非常重要。

    在对象存储领域,这意味着您可以适合归档用例(一次写入,非常少地读取),或者您选择不同的架构。

  3. 与一致性问题类似,持久性保证也是性能和正确性之间的权衡。存储引擎的默认提交日志设置为每 10 秒同步一次。这意味着在发生电源故障时,您将丢失长达 10 秒的最新更新。使 Cassandra 持久的唯一合理方法是使用同步批处理模式提交器,但这会带来性能损失。

  4. Cassandra 的高可用性保证不适合擦除编码的对象存储。对于复制因子为 3 且一致性仲裁为 2,Cassandra 只能容忍复制组中单个节点/驱动器故障。将复制因子和仲裁一致性提高到 5 或更高只会使元数据性能从糟糕变得更糟。与复制不同,擦除编码可以在分布式系统中容忍多个服务器和驱动器故障。即使您在 16 节点设置中将擦除代码设置为 6 个奇偶校验(任何 6 个节点都可能发生故障),您仍然受到最薄弱环节的限制,即 Cassandra 的复制因子。运营团队通常在为时已晚之前才意识到这些高可用性问题。

  5. 对象存储系统将数据组织在树状结构的分层命名空间中。由于 Cassandra 不支持分层键命名空间,您将不得不为每个目录前缀构建一个树形数据模型,并维护一个平面列表以进行无目录遍历的直接查找。原子地使用批处理提交日志和完整读取/写入仲裁更新多个表速度慢且容易出现损坏。

  6. 虽然对象本身是不可变的,但对象存储系统是可变的。当您添加、删除、覆盖对象及其元数据、应用策略、收集指标、授予会话令牌和轮换凭据时,元数据始终处于不断变化中。Cassandra 并非设计用于处理这种级别的元数据变异,当然也不适合主要存储工作负载。对象很大(大小为 GB)且不经常访问的长期归档用例将起作用,其他用例将不起作用。

    原因是 Cassandra 的日志结构化存储系统会快速将新的写入追加到日志文件的末尾,但会使用墓碑标记延迟删除和覆盖。清理这些墓碑是一个昂贵的操作,因为实际的删除操作是通过将 SSTables 复制到一个新表中,在此过程中筛选掉陈旧的条目来完成的。此操作必须同时在所有节点上执行。如果您延迟清理,过多的墓碑会导致读取延迟增加、内存 GC 暂停和查询失败。一些对象存储供应商使用额外的 Redis 数据库来减轻 Cassandra 的压力。使用两个数据库来管理对象存储的元数据并不优雅,并且会引入额外的故障点。

    最大的陷阱?您直到深入生产环境且为时已晚时才会看到这些问题。
  7. 小型对象(大小为 KB 到 MB)将比数据驱动器更快地填满专门用于 Cassandra 的元数据驱动器。小型对象工作负载也会加剧 Cassandra 的局限性,因为它们对延迟和一致性问题很敏感。一些供应商将小型对象完全存储在 Cassandra 中以解决这个问题。此时,您只是在 Cassandra 数据库之上查看 S3 代理。

    这也是一个不好的做法。

    如果您将对象存储用于大型对象,并使用擦除编码,并将 Cassandra 用作小型对象的数据存储,并使用复制,那么您已经引入了非平凡的 SLA 问题。在这种方法中,数据受到不同保证的保护。鉴于驱动器一直都在死机,服务旧对象或损坏对象的可能性会大大提高。

    如上所述,您的元数据数据库现在是薄弱环节。可用性、一致性和持久性保证只与最薄弱环节一样好。如果最薄弱环节使用复制(三个副本),您只能承受一个节点或一个驱动器故障,然后才会丢失数据。

    反驳意见可能是复制五个副本。结果是性能大幅下降,您仍然只能承受两个节点或两个驱动器故障。

    在对小型对象使用复制,对大型对象使用擦除编码时,您还会破坏与 EC 相关的效率提升。如果您只对大型对象(可能是整个对象池的一小部分)使用擦除代码,那么您不会获得太多收益,但会大大增加您的风险。

  8. 将 Cassandra 用作对象存储的元数据数据库还会引入麻烦的 Java 依赖项。这反过来会导致膨胀软件和内存管理问题。Cassandra 会通过持续的大规模元数据分配和变异来占用 JVM 内存管理,从而导致内存耗尽和垃圾回收暂停。

显而易见的结论是,操作 Cassandra 集群比操作设计良好的对象存储系统复杂得多。Cassandra 是为不同的目的而构建的,对象存储元数据并不是其中之一。Cassandra 难以处理的领域是对于高性能、可扩展和弹性对象存储至关重要的领域。

最后一点值得注意——对象存储非常适合 blob 数据,这就是擦除编码如此有效和高效的原因。Cassandra 是为复制而设计的。当您将此模型用于元数据时,它会破坏对象存储的擦除编码优势(或者至少使其变得脆弱且容易损坏)。

底线。原子地将您的元数据与您的对象一起写入。永远不要将它们分开。

我们欢迎您的评论。随时在 Twitter、我们的 Slack 频道或通过发送电子邮件至 hello@min.io 与我们联系。