随着数据库规模的扩大,数据分布和一致性变得更难管理。
译自 Database Scalability and the Giant Flea: A Lesson in Complexity,作者 Sunny Bains。
想想跳蚤。它是一种微小的生物,肉眼几乎看不见。但它却很强大,能够跳跃超过其身体长度 100 倍的距离。现在想象一下将跳蚤放大到人类的大小。假设你可以让它在物理上与其较小的形态完全相同,只是在每个维度上都更大。
这种放大后的超级跳蚤可以跳过 30 层楼高的建筑物。但它不能。原因在于一个称为平方立方定律的生物力学原理。随着跳蚤“放大”,其体积(质量)的增长速度快于其表面积。它长得越大,支撑自身的能力就越差。最终,跳蚤会因自身重量而崩溃。
教训是什么?你不能只是扩大一个小跳蚤来得到一个巨大的跳蚤。你需要设计一个不同的跳蚤。
可扩展性和数据库架构也是如此。一个经过优化,可以在小规模下提供 X 性能的系统,可能无法在 10,000X 规模下提供 10,000X 的性能。它的性能可能会随着规模的增长而下降。随着节点和链接数量的增加,管理数据库的复杂性也会增加。这主要是由于需要增加数据分发和节点间通信的管理,并保持所有节点之间的一致性。这可能会导致配置、监控和故障排除方面的挑战,尤其是在具有许多节点的大型集群中。添加的节点越多,就越难跟踪数据位于何处以及如何跨集群有效地访问数据。
复杂性有两个主要来源:技术性和运营性。顾名思义,技术复杂性是指创建可以在大规模下运行的系统的纯技术方面。这些可能包括自动扩展、管理容器、分配存储、配置复杂的网络以及抽象手动操作。大多数人在考虑扩展软件时都会想到这些挑战。
运营复杂性是技术复杂性的另一面。它指的是在大规模下管理和使用系统的难度。它是技术系统对其运营商征收的时间和劳动力税。它是所有未自动化的但必须完成才能使系统发挥作用的工作。由于它包含在难以量化的人为因素中,因此运营复杂性不如技术复杂性那样受到重视和理解。不过,我认为它同样重要。
在云中,我们已经弄清楚了如何构建可以根据需要增长的数据库。通过抽象超大规模基础设施的令人难以置信的复杂性,云使得构建技术上简单的系统成为可能,同时提供“速度和反馈”,这在不久前还是令人难以置信的。
这听起来很棒。但也有不利的一面。我们创建了技术上可以在任何规模下运行,但在大规模下运营不切实际的数据库。作为一个行业,我们仍在努力解决运行和管理超大规模系统的人为问题。
这种现象的一个经典例子是分片,它是将数据存储细分为更易于管理的片段的做法。最常见的分片形式“naive sharding”假设数据均匀分布(这是一个重要的假设,但我们稍后会讨论)。性能非常出色。延迟很低。吞吐量很高。一切都很好。从技术的角度来看,分片似乎是保持数据库性能随着增长的好方法。
从运营的角度来看,情况有所不同。对数据库进行分片会产生一个新问题:在分片中定位数据。在 naive sharding 中,数据库不知道给定的数据位于何处。“分片键”信息位于应用层。这意味着跟踪数据现在是开发人员的问题。 当数据库相对较小(比如 10 GB)且分片数量相对较少时,这是一项可管理的任务。但现在,数据库已增长到 100 GB 或 200 GB。现在,每个分片的大小都是初始分片时整个数据库大小的许多倍。数据不再在分片之间平均分配。因此,开始出现大量热点。性能直线下降。是时候重置数据库了。
现在,运维复杂性开始显现。必须手动重新分配和重新平衡数据,创建新的分片键。已经硬编码到现有应用程序中的分片键必须更新,因为此设计中的分片是显式的。应用程序必须知道数据的位置。需要审查使用分片键的每一行代码。以前位于同一分片上的数据现在必须手动分配到单独的分片,并且需要相应地重构代码。
这项任务简直是噩梦。没有 grep 查找和替换可以自动处理它。由于其局限性,人类必须努力应对分片所产生的运维复杂性。
随着组织向系统添加服务器或节点,运维复杂性会增加。通常添加节点是为了冗余和负载共享,因此一个节点上的更改必须反映在其他节点上,或者必须分散数据以避免热点。移动数据与重新分片相同。您希望集群中数据的移动对用户不可见。这是分布式 SQL 前端节点从任何节点获取数据的工作。
在大多数数据库(包括 MySQL)中,在最坏的情况下,由于复制滞后,提交到一个节点的更改可能需要几秒甚至几分钟才能在其他节点上注册。例如,复制滞后的一个罪魁祸首是 DDL。主节点上的 DDL 操作可能会阻止事件传播到从节点。因此,在更改传播到数据库中的所有节点之前,用户可能会根据其查询指向的节点看到不同的结果。这是一种被称为“最终一致性”的权衡。这是 MySQL 和 Amazon Aurora、MySQL Postgres 以及任何基于 MySQL 模型构建的数据库的规则。(从技术上讲,MySQL Group Replication 确实提供了一种强一致性模式,但很少使用,因为文档表明使用该模式的风险自负。)
为什么这很重要?如果所讨论的更改是社交帖子上的“赞”,则可能无关紧要。但是,如果有人从银行取款或在更改密码后尝试登录,那么每个节点是否同时具有相同的信息就非常重要了。
同样,负担落在开发人员身上。由于底层系统不保证强一致性,因此开发人员需要在代码中设计一种解决方法。这些限制在单节点数据库中并不明显。它们是架构决策的结果,这些决策在较小规模上是完全合理的。但是,这种较弱的一致性模式使开发人员的工作更加困难和复杂,因为系统会不断增长。
在软件工程中,就像在生活中一样,没有免费的午餐。减少自动化和抽象(即技术复杂性),您会获得更多的运维复杂性。简化操作,您需要更复杂的技术来补偿。NoSQL 数据库 MongoDB 的部分灵感来自于对基于模式的 DDL 操作的沮丧。但是,像 MongoDB 和其他 NoSQL 解决方案一样,剥离模式会产生可扩展性问题。除其他问题外,这使得在大型组织中运行分析更具挑战性。正如我在其他地方写的那样,这就是为什么 NoSQL 实现往往会大规模失败的原因。这并不是因为它们缺乏纯粹的技术能力,而是因为它们难以满足大型企业的运营需求。
复杂性是不可避免的。这只是一个问题,它采取什么形式,以什么比例——以及谁来“支付”它。我们无法消除技术和运维复杂性,但我们可以尝试在极端之间找到一个中间地带。
目前最新的尝试是像 CockroachDB 和 TiDB 这样的分布式 SQL 解决方案,它们基于 Google 的 Spanner 项目背后的技术。分布式 SQL 系统依赖于传统的关系模式,但可以自动实现水平扩展、DDL 复制以及其他一些在历史上限制了结构化数据库扩展能力的功能。分布式 SQL 的目标不是要达到 NoSQL 数据库所能提供的原始存储和检索速度水平,也不是要复制单节点 MySQL 数据库的纯粹简单性。其目标是在性能(即速度、延迟)、可靠性、多功能性和易管理性之间找到一个平衡点。在实践中,这意味着增加底层技术复杂性,以减少最终用户的运营复杂性负担。
目前,大多数大型数据系统就像我们假设的巨型跳蚤,正处于接近崩溃的阶段。它们仍然可以被识别出来。但是,它们的功能比它们体积更小时更差。而且它们越大,情况就越糟。
大多数数据库开发都是关于重新设计跳蚤。这可能有很多形式。分布式 SQL 是一种方法,还有其他可能的方法。无论他们如何解决这个问题,他们都必须找到解决运营复杂性的方案。我们现在拥有的是纸面上的规模。企业需要的是实践中的规模。