Redian新闻
>
存储量扩大千倍,Discord 是如何使用Rust语言和ScyllaDB数据库来改进架构的?

存储量扩大千倍,Discord 是如何使用Rust语言和ScyllaDB数据库来改进架构的?

公众号新闻


作者 | Bo Ingram
译者 | 平川
策划 | Tina

本文最初发布于 Discord 官方博客。

2017 年,我们写了一篇关于我们如何存储数十亿条消息的博文,分享了我们开始时如何使用 MongoDB,但又将数据迁移到 Cassandra 的过程,因为我们正在寻找一个扩展性和容错性比较高而维护成本相对较低的数据库。我们确信自己会发展,而且我们确实做到了!

我们想要一个能随着我们的发展而演进的数据库,但又不希望它的维护需求会随着我们的存储需求而增长。遗憾的是,我们发现事实并非如此——我们的 Cassandra 集群出现了严重的性能问题,光是维护就需要花费很多的精力,更不用说改进了。

近 6 年过去了,我们已经改变了很多,我们存储消息的方式也发生了变化。‍

Cassandra 的麻烦

我们把信息存储在一个名为 cassandra-messages 的数据库中。顾名思义,它运行 Cassandra 来存储消息。2017 年,我们运行了 12 个 Cassandra 节点,存储了数十亿条消息。

2022 年初,节点数达到 177 个,而消息有数万亿条。令人懊恼的是,这是一个高强度的系统——我们的随叫随到团队经常接到反馈数据库问题的电话,延迟不可预测,因为成本过高,我们不得不减少维护操作。

是什么导致了这些问题?首先,让我们来看一条消息。

CREATE TABLE messages (   channel_id bigint,   bucket int,   message_id bigint,   author_id bigint,   content text,   PRIMARY KEY ((channel_id, bucket), message_id)) WITH CLUSTERING ORDER BY (message_id DESC);

上面的 CQL 语句是我们最小的消息模式版本。我们使用的每个 ID 都是用雪花算法生成的,按时间顺序排序。我们根据消息的发送通道以及桶(一个静态时间窗口)进行消息分区。这种分区意味着,在 Cassandra 中,特定通道和桶的所有消息将存储在一起,并在 3 个节点(取决于设置的复制因子)上复制。

这种分区有潜在的性能缺陷:只有一小群人使用的服务器发送的消息往往比有数十万人使用的服务器少几个数量级。

在 Cassandra 中,读的开销比写大。写操作会被追加到提交日志,并写入内存中一个名为 memtable 的结构,最后再刷写到磁盘。然而,读操作需要查询 memtable,还可能要查询多个 SSTable(磁盘上的文件),其成本更高。当用户与服务器交互时,大量的并发读取会使一个分区成为热点,我们可以称其为“热分区”。这些访问模式在遇到我们的数据集规模时,导致我们的集群陷入了困境。

当我们遇到热分区时,它经常会影响整个数据库集群的延迟。一个通道 - 桶对接收了大量的流量,节点为之提供服务会越来越吃力,延迟会越来越大,越落越远。

该节点上的其他查询也会受到影响,因为它的速度跟不上。由于我们的读写操作都是仲裁一致性级别的,所以在为热分区提供服务的节点上,所有查询的延迟都会增加,进而对最终用户产生更广泛的影响。

集群维护任务也经常引起麻烦。我们很容易在压缩上落后,为了获得更高的读性能,Cassandra 会压缩磁盘上的 SSTable。这样一来,不仅读取的开销增大,而且当节点试图压缩时,还会产生级联延迟。

‍我们经常执行一种我们称之为“八卦舞”的操作。我们让一个节点退出轮换,让它在停止接收流量的情况下进行压缩,然后让它重新加入轮换,从 Cassandra 获取暗示切换线索,然后再重复,直到待压缩项为空。我们还花了大量时间对 JVM 的垃圾收集器和堆设置进行调优,因为 GC 暂停会导致显著的延迟尖峰。

改进架构

消息集群并不是我们唯一的 Cassandra 数据库。我们还有其他几个集群,每个集群都展现出类似的缺陷(尽管可能没有那么严重)。

在上文提到的那篇文章中,ScyllaDB 引起了我们的兴趣,那是一个用 C++ 编写的数据库,兼容 Cassandra。它承诺提供更好的性能、更快的修复、更强的工作负载隔离(通过其按核分片架构),而且无垃圾回收,听起来相当吸引人。

尽管 ScyllaDB 也不一定没问题,但它没有垃圾收集器,因为它是用 C++ 而不是 Java 编写的。长期以来,我们的团队在 Cassandra 的垃圾收集器上遇到过许多问题,从 GC 暂停影响延迟,到连续超长时间的 GC 暂停,甚至运维人员必须手动重启问题节点才能将其恢复到健康状态。这些问题导致了大量的随叫随到工作,也是我们消息集群中许多稳定性问题的根源。

在对 ScyllaDB 进行试验并在测试中观察改进效果之后,我们决定迁移所有的数据库。虽然这个决定本身可以单独写成一篇博文,但简单来说,截止 2020 年,除一个数据库之外,我们已经将其他所有的数据库都迁移到了 ScyllaDB 上。

最后剩下的那个是我们的朋友,cassandra-messages。

为什么我们还没有迁移它呢?首先,这是一个很大的集群,有数万亿条消息和近 200 个节点,任何迁移工作都会很复杂。此外,我们对新数据库进行了性能调优,希望它们能够达到最佳状态。我们还希望能够积累更多在生产环境使用 ScyllaDB 的经验,了解它的陷阱。

我们还针对我们的用例改进了 ScyllaDB 的性能。我们在测试中发现,反向查询的性能不足以满足我们的需求。在以与表排序相反的顺序扫描数据库时,例如按升序扫描消息时,将执行反向查询。ScyllaDB 团队优先改进并实现了高性能的反向查询,为我们的迁移计划消除了最后的数据库障碍。

我们并没有指望在系统上加一个新数据库就能让一切神奇地变好。热分区在 ScyllaDB 中仍然存在。因此,我们还希望投资改进数据库上游系统,为数据库增加一道屏障,进一步提升数据库的性能。

用数据服务提供数据

对于 Cassandra,我们遇到了热分区的麻烦。到特定分区的高流量会导致无限并发,进而导致级联延迟,后续查询的延迟会继续增加。如果可以控制热分区的并发流量,我们就可以保护数据库不被压垮。

为了完成这项任务,我们编写了所谓的数据服务——介于 API 单体和数据库集群之间的中介服务。在编写数据服务时,我们选择了一种在 Discord 中应用越来越多的语言:Rust。我们在之前的几个项目中用过它,它没有辜负我们的期望。它为我们提供了媲美 C/C++ 的速度,而且没有牺牲安全性。

无惧并发是 Rust 引以为豪的主要优势之一——该语言使编写安全并发代码变得更容易。它提供的库也非常符合我们的预期。Tokio 生态系统是构建异步 I/O 系统的坚实基础,并且该语言提供了 Cassandra 和 ScyllaDB 的驱动程序。

此外,我们还发现,Rust 编译器提供的帮助、清晰的错误消息、语言结构及其对安全性的重视,让编码变得很有乐趣。我们非常喜欢的一点是,Rust 程序一旦通过编译,通常就可以运行。不过,最重要的是,我们可以说我们用 Rust 进行了重写(模因声誉非常重要)。

我们的数据服务介于 API 和 ScyllaDB 集群之间。大致上,它们为每个数据库查询提供一个 gRPC 端点,并且故意不包含业务逻辑。数据服务的一大特色是请求合并。如果多个用户同时请求同一行,我们将只查询数据库一次。第一个发出请求的用户会触发数据服务中的工作者任务。后续请求将检查该任务是否存在并订阅它。该工作者任务将查询数据库并把行返回给所有订阅者。

这就是 Rust 的强大之处:它使编写安全并发代码变得更简单。

让我们想象一下,在一个大型服务器上,有一条 @所有人的重要公告:用户将打开应用程序并阅读消息,向数据库发送大量流量。以前,这可能会导致热分区,并且可能需要随叫随到工程师帮助恢复系统。通过数据服务,我们能够显著降低数据库的流量峰值。

魔法的第 2 部分在数据服务的上游。为了实现更有效的合并,我们实现了一致的基于哈希的数据服务路由。我们为每个数据服务请求提供一个路由键。对于消息,这是一个通道 ID。这样一来,对同一通道的所有请求都会发送到服务的同一实例。这种路由方式帮助我们进一步减少了数据库的负载。

这些改进对我们帮助很大,但并不能解决所有问题。我们仍然会在 Cassandra 集群上看到热分区和延迟增加,只是不那么频繁了。那为我们赢得了一些时间,让我们可以准备最优的 ScyllaDB 集群并执行迁移。‍

一次规模非常大的迁移

我们的迁移需求非常简单:我们需要在不停机的情况下迁移数万亿条消息,而且需要快速完成,因为虽然 Cassandra 的情况有所改善,但我们还是经常处于灭火状态。

第一步很简单:使用超级磁盘存储拓扑准备一个新的 ScyllaDB 集群。借助本地 SSD 来提高速度,并利用 RAID 将数据镜像到持久盘。这样,我们既从附加的本地磁盘那里获得了速度,又从持久盘那里获得了持久性。集群启动后,我们就可以开始向其中迁移数据了。

我们第一版的迁移计划旨在快速获取价值。我们开始使用崭新的 ScyllaDB 集群来处理新数据,然后找一个切换时间迁移历史数据。这带来了更多的复杂性,但每个大型项目都会有额外的复杂性,不是吗?

对于新数据,我们开始执行双重写入,即同时写入 Cassandra 和 ScyllaDB。与此同时,我们还开始准备 ScyllaDB 的 Spark 迁移器。那需要大量的调整,设置完成之后,我们估计了完成时间:3 个月。

对于这个期限,我们并不满意。我们希望可以更快地获取价值。因此,我们团队组织了一场头脑风暴,看看如何加快速度,直到我们记起来,我们已经编写了一个快速的高性能数据库库,我们可以对它进行扩展。我们选择参与了一些模因驱动工程,并用 Rust 重写了数据迁移器。

有一天下午,为了执行大规模数据迁移,我们扩展了数据服务库。它从数据库中读取令牌范围,通过 SQLite 在本地进行检查,然后将它们送入 ScyllaDB。我们连接到经过改进的新迁移器,并重新估计了工期:9 天!如果能够这么快的迁移数据,我们就可以抛开我们基于时间的复杂方法,一次性地切换所有内容。

我们启动它并让它保持运行,以每秒 320 万条消息的速度迁移。几天后,我们看到迁移已达 100%。不过我们意识到,它被卡在了 99.9999%(是真的)。我们的迁移器在读取数据的最后几个令牌范围时超时了,因为它们包含了巨大的墓碑范围,而且从未压实。在我们把那个令牌范围压实几秒钟后,迁移就完成了!

通过向两个数据库发送一小部分读数请求并比较结果,我们完成了自动数据验证,一切看起来都很好。在全生产流量的情况下,集群依然运行良好,而 Cassandra 却遇到了越来越频繁的延迟问题。我们的团队聚在现场,按下开关,让 ScyllaDB 成为主数据库,并分享了庆祝蛋糕!

数月之后……

2022 年 5 月,我们切换了消息数据库,但自那以后它的运行状况如何呢?

它是一个安静、乖巧的数据库(这么说没关系,因为这周我不用随叫随到)。我们周末不用长时间救火了,也不用为了保持正常运行时间而同时处理多个集群节点。这个数据库更高效——我们的 Cassandra 节点有 177 个,而 ScyllaDB 节点只有 72 个。每个 ScyllaDB 节点有 9TB 的磁盘空间,而每个 Cassandra 节点的平均磁盘空间为 4TB。

我们的尾部延迟也得到了大幅改善。例如,从 Cassandra 获取历史消息的 p99 延迟在 40-125 毫秒之间,在 ScyllaDB 上只有 15 毫秒;向 Cassandra 插入消息的 p99 延迟在 5-70 毫秒之间,而 ScyllaDB 为稳定的 5 毫秒。得益于前面提到的性能改进,我们还解锁了新的产品用例。现在,我们对消息数据库很有信心。

2022 年底,全世界的人都在收看世界杯。我们很快就发现,监控图上显示了总进球数。这非常酷,因为那不仅让我们可以在系统中看到真实世界的事件,还让我们团队在会议期间观看足球比赛有了正当的理由。我们不是“在会议期间观看足球比赛”,而是“在主动监控系统的性能”。

实际上,我们可以通过消息发送图来讲述世界杯决赛的故事。这场比赛非常精彩。梅西试图完成职业生涯的最后一项成就,带领阿根廷队夺得冠军,但才华横溢的姆巴佩和法国队试图阻挡他的前进之路。

图中的 9 个尖峰代表比赛中的 9 个事件:

1. 梅西罚进点球,阿根廷 1-0 领先。

2. 阿根廷再次得分,2-0 领先。

3. 中场休息。用户谈论比赛,有一个持续 15 分钟的平稳期。

4. 这里的大尖峰是因为姆巴佩为法国队进球,90 秒后又进球扳平了比分!

5. 常规时间的比赛结束了,这场重要的比赛将进入加时赛。

6. 加时赛的前半段什么也没发生,但到了中场休息时,用户们在聊天。

7. 梅西再次进球,阿根廷取得领先!

8. 姆巴佩反击扳平!

9. 加时赛结束了,要踢点球了!

10. 在点球大战中,兴奋感和压力不断增加,直到法国队罚丢,而阿根廷队命中!阿根廷赢了!

每秒合并消息数

全世界的人们都在观看这场不可思议的比赛,但与此同时,Discord 和消息数据库却毫无压力。我们在信息发送和处理方面做得很好。我们基于 Rust 的数据服务和 ScyllaDB 能够承受这些流量,并为用户提供一个交流的平台。

原文链接:

https://discord.com/blog/how-discord-stores-trillions-of-messages

    声明:本文为 InfoQ 翻译,未经许可禁止转载。

你也「在看」吗? 👇

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Rust 基础系列 #3: Rust 中的数据类型 | Linux 中国15年做不好的代码搜索,用Rust重写搞定:GitHub声称能从此“改变游戏规则”谷歌宣布支持使用Rust开发Chromium将 sudo 和 su 移植到 Rust,Rust 要重写万物?渐近式 AI 编程架构:Unit Mesh 架构的设计与探索Rust 语言年度回顾:全球企业如何采用 Rust的?采用 Rust 语言编写,System76 公布 COSMIC 桌面环境开发近况数据库er的夏日盛宴 | 2023 可信数据库发展大会演讲议题征集限时开启!"𝙇𝙚𝙖𝙙 𝙏𝙝𝙚 𝘾𝙝𝙖𝙧𝙜𝙚"广告#创译挑战临床数据库对于临床科研有多重要?如何利用开放的数据库撰写SCI论文?Rust 基础系列 #2: 在 Rust 程序中使用变量和常量 | Linux 中国Rust 基础系列 #1: 创建并运行你的首个 Rust 程序 | Linux 中国美国入境档案--黎锦扬Cloudflare用Rust重写Nginx C模块,构建没有Nginx的未来lāo dao?láo dao!Fish Shell计划采用Rust重写卷起来!Dr. LLaMA:通过生成数据增强改进特定领域 QA 中的小型语言模型,重点关注医学问答任务税务局建议你在网上报税 以下是如何使用最新的NETFILE最全的生物样本数据库(Biobank)收集;及部分公开数据库的大数据分享多模数据库、“Serverless 化” | 开源数据库领域解读祝福与期待——除夕遐想每分钟可处理8.14亿笔交易、腾讯云数据库TDSQL刷新TPC-C纪录对国产数据库行业意味着什么?衍生品售卖超目标千倍,《流浪地球2》为何冲破影视IP天花板?【南北碰碰胡】# 26南方 + 《恭喜恭喜》+ 广东煎堆油角选择“网红语言”值不值?使用Go和Rust的数据库公司七年经验总结大家过年好!年夜饭来了:-)直面业务出海难题:强合规架构、数据合规、多云架构的实践经验|QCon广州AIGC教程:如何使用Stable Diffusion生成风格化游戏物品和图标印度电影《RRR》观后感(1)慢SQL是如何拖垮数据库的?AIGC教程:如何使用Stable Diffusion高效生产“等距游戏美术资源”?Rust将迎来爆发式增长;更多国产编程语言进入视野 | 编程语言领域解读震惊!使用RNN就能达到超越GPT的对话效果!甚至超越LLaMA? Github已近万starAIGC教程:如何使用Stable Diffusion,实现PBR材质纹理自由?英伟达超快StyleGAN回归,比Stable Diffusion快30多倍,网友:GAN好像只剩下快了
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。