Apache Cassandra 中的性能缺陷

2021-08-08 11:49:56

最近,我正在对几个不同的数据库进行消息传递工作负载的评估。在对 Apache Cassandra 进行基准测试时,我注意到性能指标中的异常模式。我遵循了这些线索,最终发现了一些主要的线程池设计问题,并在 Windows 上实现了潜在的 18 倍性能提升。我一直在研究一系列不同的数据库作为重新设计消息产品的潜在后端。虽然我已经想到了一些候选人,但我希望能够展示对数据库选项的强大探索。虽然 PostgreSQL 是 SQL 数据库中的有力竞争者,并且在早期实验中取得了不错的结果,但我一直在寻找理想的 NoSQL 候选者。 Apache Cassandra 比最新的 NewSQL 和键值数据库要老一些,但它在很多方面似乎在架构上都很理想:我的目标是将数据库几乎完全用作“只写数据存储”。消息传递有两个主要要求:持久记录已完成的工作(接收和发送的消息)以允许崩溃恢复,以及记录更长期的可搜索日志。 Cassandra 已经被我们的工程师确定为首选数据库,理论上应该非常符合这些要求。凭借高度可扩展的分区写入性能及其 LSMT 数据结构能够自动从表中压缩已完成的工作,它似乎是一个明确的领导者。我在消息传递系统中评估的主要工作负载涉及接收消息、捕获属性、转换消息并发送它们。

在许多客户系统中,将有一个主要路由按顺序处理大多数。需要持久地记录这些消息的接收情况,这使得它本质上是一个单线程用例,因此这是一个主要的基准测试。还有多线程基准测试可以为在多条路线上更均匀地分配工作的客户表征性能。 PostgreSQL 在单线程和多线程基准测试中都取得了优异的成绩,但我希望 Cassandra——凭借其更简单的模型和极致的性能——能够提供更高的性能。然而,这些结果表明单线程写入性能存在一些相关异常。存在几个关键问题: 单线程 Cassandra 基准测试在硬件上表现出极差的性能,与 PostgreSQL 显示的物理性能相比。延迟与排队理论表明的通常模式相反;在多线程系统中,更多的线程应该提供更高的吞吐量,但以增加延迟为代价。 Cassandra 展示了这种模式的反转。吞吐量增加超过线程数——10 倍的线程提供远远超过 10 倍的吞吐量这一事实也是可疑的。综上所述,这些症状让我怀疑 Cassandra 上的单线程工作负载可能存在一些效率低下的问题。

对于像我这样不熟悉的工程师来说,了解 Cassandra 的内部执行过程具有很大的复杂性。具有很强的并发性,通过多个工作池跨多个线程处理数据库操作。鉴于这种复杂性,检测和跟踪请求处理似乎是理解问题的唯一可行途径。所以我获取了源代码,构建了 Cassandra 并开始添加自定义日志来检测问题。我的日志记录集中在记录整个请求的开始和结束时间以及转移到工作池的组件任务的开始和结束时间。这里的目标是能够在微秒级别跟踪任务实际执行的时间,以寻找延迟。跟踪显示在调用 StorageProxy.performLocally() 和实际执行 LocalMutationRunnable 之间的平均延迟为 1.52 毫秒。总操作时间平均为 2.06 毫秒(在 Message.Dispatcher processRequest() 处测量)。这表明在 SEPExecutor 中等待线程调度所损失的总操作时间的约 72%。鉴于我发现的延迟,SEPExecutor 线程池成为调查的焦点。这是一个 Cassandra 特定的自定义线程池,具有显着的内部复杂性。我用 SEPExecutor 尝试了许多笨拙的干预措施,但没有一次完全成功。

我开始询问有关 SEPWorker.assign() 在从 SPINNING 转换到“工作”状态时如何不解除线程的问题。到目前为止,我已经记录了我的调查和结果,我在 Cassandra JIRA 错误跟踪器上提出了一个问题。最初它遇到了一些公平的问题,但老实说有一定程度的怀疑。 (老实说,这对于任何项目都是可以理解的,因为项目拥有的资源有限,而且他们可能会收到大量愚蠢的问题和误报。)与此同时,我正在寻找和讨论关于 JIRA 问题的进一步线索。 Cassandra 的一位负责人加入了这个问题,并正在讨论典型的环境和用例。我愿意听到一些见解或解释,但我自己坚信这里很可能会找到一些东西。对任务调度行为和由此产生的调度延迟的详细跟踪表明,当工作人员已经可用但停在“旋转”状态时会发生延迟。在这种情况下,Cassandra 的 SEPExecutor 线程池既没有启动额外的 worker,也没有唤醒(unpark)正在休眠的 worker。在单线程条件下,worker 会在每次请求后立即进入睡眠状态;最多需要 1.5 毫秒才能唤醒下一个!虽然这对我来说似乎是强有力的证据,但 Cassandra 社区指出线程池是为 Linux 设计的,并且(从新版本 4 开始)他们已经放弃了对 Windows 作为平台的支持。

所以,我去拿了一个 EC2 实例并进行了一些 Linux 测试。在单线程情况下,我能够在 Linux 上发现 +30.9% 的性能改进,在 10、50 和 200 个线程情况下,改进从小到大不等。在这一点上,我觉得我已经报告了一个明显的错误 (CASSANDRA-16499),提供了一个补丁,并显示了性能改进和非回归的良好证据。然而,提出了架构问题,这是执行器的预期设计——工作线程自组织,生产者和消费者之间的交互有限。添加主动唤醒行为将消除此设计功能。 Cassandra 领导并没有接受补丁,而是要求对可能的线程池选项进行全面的架构重新评估。虽然这是一次有价值的探索,但我在基准测试和评估过程中发现了 Cassandra 的其他缺点:并行提交/fsync 可能效率低下——PostgreSQL 能够在单个 fsync() 中提交多个等待事务,Cassandra 可能在这方面有局限性我们讨论了我们的数据库选项,以及进一步投资 Cassandra 以发现和修复这些其他可能的缺陷的潜力。这是一个很棒的平台,我们很感兴趣,但我们很难做出商业案例。

所以感谢阅读,我希望你觉得这很有趣!如果有人希望继续这项工作,我很乐意讨论。