今年(2021)早些时候,我们对 Notion 进行了五分钟的定期维护。虽然我们的声明指向“提高稳定性和性能”,但在幕后是数月专注、紧迫的团队合作的结果:将 Notion 的 PostgreSQL 整体分片成一个水平分区的数据库舰队。
分片命名法被认为起源于 MMORPG Ultima Online,当时游戏开发者需要一个宇宙解释来解释存在多个运行平行世界副本的游戏服务器。具体来说,每一个碎片都是从一个破碎的水晶中出现的,邪恶的巫师蒙丹曾试图通过它夺取世界的控制权。
Database “sharding” came from UO?
https://uo.com/
虽然转换成功让大家欢欣鼓舞,但我们仍然保持沉默,以防迁移后出现任何问题。令我们高兴的是,用户很快开始注意到改进。完全是 “show don't tell”。
让我告诉你我们如何分片的故事以及我们在此过程中学到的东西。
分片是我们不断努力提高应用程序性能的一个重要里程碑。在过去的几年里,看到越来越多的人将 Notion 应用到他们生活的方方面面,这令人欣慰和谦卑。不出所料,所有新的公司 wiki、项目跟踪器和图鉴都意味着数十亿新的blocks、files 和 spaces。到 2020 年年中,很明显,产品的使用将超过我们值得信赖的 Postgres 单体的能力,后者在五年和四个数量级的增长中尽职尽责地为我们服务。随叫随到的工程师经常被数据库 CPU 峰值唤醒,简单的仅目录迁移变得不安全和不确定。
在分片方面,快速发展的初创公司必须进行微妙的权衡。在此期间,大量博客文章过早地阐述了分片的危险:增加的维护负担、应用程序级代码中新发现的约束以及架构路径依赖性。¹当然,在我们的规模上,分片是不可避免的。问题只是什么时候。
对我们来说,当 Postgres VACUUM 进程开始持续停止时,拐点就到了,阻止了数据库从死元组中回收磁盘空间。虽然可以增加磁盘容量,但更令人担忧的是 transaction ID (TXID) wraparound,这是一种 Postgres 将停止处理所有写入以避免破坏现有数据的安全机制。意识到 TXID wraparound 会对产品构成生存威胁,我们的基础架构团队加倍努力并开始工作。
https://blog.sentry.io/2015/07/23/transaction-id-wraparound-in-postgres
如果您以前从未对数据库进行过分片,那么这里的想法是:不要使用越来越多的实例垂直扩展数据库,而是通过跨多个数据库分区数据来水平扩展。现在,您可以轻松启动其他主机以适应增长。不幸的是,现在您的数据位于多个位置,因此您需要设计一个在分布式环境中最大限度地提高性能和一致性的系统。
为什么不保持垂直缩放?正如我们发现的那样,使用 RDS“调整实例大小”按钮玩 Cookie Clicker 并不是一个可行的长期策略——即使你有预算。查询性能和维护过程通常在表达到最大硬件绑定大小之前就开始下降;我们停止的 Postgres auto-vacuum 就是这种软限制的一个例子。
我们决定实现我们自己的分区方案并从应用程序逻辑路由查询,这种方法称为应用程序级分片。在我们最初的研究中,我们还考虑了打包的分片/集群解决方案,例如用于 Postgres 的 Citus 或用于 MySQL 的 Vitess。虽然这些解决方案因其简单性而吸引人,并提供开箱即用的跨分片工具,但实际的集群逻辑是不透明的,我们希望控制数据的分布。²
https://www.citusdata.com/
https://vitess.io/
应用程序级分片要求我们做出以下设计决策:
由于 Notion 的数据模型围绕块的概念展开,每个块在我们的数据库中占据一行,因此 block(块) 表是分片的最高优先级。但是,块可能会引用其他表,例如space(工作区)或 discussion(page-level 和 inline discussion 线程)。反过来,discussion 可能会引用 comment 表中的行,等等。
https://www.notion.so/blog/data-model-behind-notion
我们决定通过某种外键关系对所有可从 block 表访问的表进行分片。并非所有这些表都需要分片,但是如果一条记录存储在主数据库中,而其相关块存储在不同的物理分片上,我们可能会在写入不同的数据存储时引入不一致。
例如,考虑一个存储在一个数据库中的块,在另一个数据库中具有相关的评论。如果块被删除,评论应该被更新 — 但是,由于事务性保证只适用于每个数据存储,所以块删除可能成功,而评论更新可能失败。
一旦我们决定分片哪些表,我们就必须将它们分开。选择一个好的分区方案很大程度上取决于数据的分布和连通性;由于 Notion 是基于团队的产品,我们的下一个决定是按 workspace ID 对数据进行分区。³
每个工作空间在创建时都分配了一个 UUID,因此我们可以将 UUID 空间划分为统一的存储桶。因为分片表中的每一行要么是一个块,要么与一个块相关,并且每个块都属于一个工作区,所以我们使用 workspace ID 作为分区键(partition key)。由于用户通常一次在单个工作空间内查询数据,因此我们避免了大多数跨分片连接。
决定了分区方案后,我们的目标是设计一个分片设置,以处理我们现有的数据和规模,以轻松满足我们两年的使用预测。以下是我们的一些限制条件:
https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_Storage.html
在计算了数字之后,我们确定了一个由 480 个逻辑分片(logical shards)组成的架构,这些分片均匀分布在 32 个物理数据库中。层次结构如下所示:
block 表(每个逻辑分片 1 个,总共 480 个)
collection 表(每个逻辑分片 1 个,总共 480 个)
space 表(每个逻辑分片 1 个,总共 480 个)
等所有分片表
逻辑分片,表示为 Postgres 模式(每个数据库 15 个,总共 480 个)
您可能想知道,“为什么要 480 个分片?我认为所有计算机科学都是以 2 的幂次方完成的,这不是我认识的驱动器大小!”
有很多因素导致选择 480:
关键是,480 可以被很多数字整除——这提供了添加或删除物理主机的灵活性,同时保持统一的分片分布。例如,将来我们可以从 32 台扩展到 40 台再到 48 台主机,每次都进行增量跳跃。相比之下,假设我们有 512 个逻辑分片。512 的因数都是 2 的幂,这意味着如果我们想保持分片均匀,我们会从 32 台主机跳到 64 台主机。任何 2 的幂都需要我们将物理主机的数量增加一倍以进行升级。选择具有很多因素的值!
我们从包含每张表的单个数据库发展为由 32 个物理数据库组成的舰队,每个数据库包含 15 个逻辑分片,每个分片包含每个分片表中的一个。我们总共有 480 个逻辑分片。
我们选择将 schema001.block、schema002.block 等构建为单独的表,而不是为每个数据库维护一个具有 15 个子表的分区 block 表。原生分区表引入了另一条路由逻辑:
https://www.postgresql.org/docs/10/ddl-partitioning.html
保留单独的表允许我们直接从应用程序路由到特定的数据库和逻辑分片。
我们想要从 workspace ID 路由到逻辑分片的单一事实来源,因此我们选择单独构建表并在应用程序中执行所有路由。
一旦我们建立了分片方案,就该实施它了。对于任何迁移,我们的一般框架都是这样的:
双写阶段确保新数据同时填充新旧数据库,即使新数据库尚未使用。双写有几种选择:
https://www.postgresql.org/docs/10/logical-replication.html
我们选择了 audit log 策略而不是逻辑复制,因为后者在初始快照步骤中难以跟上 block 表写入量。
https://www.postgresql.org/docs/10/logical-replication-architecture.html#LOGICAL-REPLICATION-SNAPSHOT
我们还准备并测试了一个反向审计日志和脚本,以防我们需要从分片切换回单体应用。该脚本将捕获对分片数据库的任何传入写入,并允许我们在单体应用程序上重放这些编辑。最后,我们不需要恢复,但这是我们应急计划的重要组成部分。
一旦传入的写入成功传播到新数据库,我们就会启动回填过程以迁移所有现有数据。使用我们预置的 m5.24xlarge 实例上的所有 96 CPUs(!),我们的最终脚本大约需要三天时间来回填生产环境。
任何值得称道的回填都应该在写入旧数据之前比较记录版本,跳过具有最近更新的记录。通过以任何顺序运行追赶脚本和回填,新数据库最终将聚合以复制整体。
迁移仅与底层数据的完整性一样好,因此在分片与单体应用保持同步后,我们开始验证正确性的过程。
Re-architecting Slack’s Workspace Preferences: How to Move to an EAV Model to Support Scalability
作为预防措施,迁移和验证逻辑是由不同的人实现的。否则,在两个阶段都犯同样错误的可能性更大,削弱了验证的前提。
虽然分片项目的大部分内容都让 Notion 的工程团队处于最佳状态,但我们事后会重新考虑许多决定。这里有一些例子:
尽管有这些假设,分片还是取得了巨大的成功。对于 Notion 用户来说,几分钟的停机时间使产品明显更快。在内部,我们在时间敏感的目标下展示了协调的团队合作和果断的执行力。
https://aws.amazon.com/ec2/instance-types/i3en/
https://www.startuplessonslearned.com/2009/01/sharding-for-startups.html?m=1#comment-form
网站栏目:从Notion分片Postgres中吸取的教训
转载来于:http://www.mswzjz.cn/qtweb/news46/421346.html
攀枝花网站建设、攀枝花网站运维推广公司-贝锐智能,是专注品牌与效果的网络营销公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 贝锐智能