为什么规划数据库容量如此困难?那么怎么简化?可以使用开源NoSQL数据库ScyllaDB来演示示例。
调整数据库的大小看起来很简朴:用数据集的大小和所需吞吐量除以节点的容量。很容易,不是吗?
如果有人曾经尝试规划数据库容量,就会知道这有多难。即使是做出粗略的估计也很具挑战性。那么为什么这么难?
以下是估算集群大小的步调:
(1)对使用模式做出假设。
(2)估计所需工作量。
(3)决定数据库的高级配置。
(4)将工作负载、配置和使用模式提供给数据库的性能模子。
(5)收入。
这个流程虽然容易理解,但在实践工作中却不那么容易。
比方,在对数据库配置(比方复制因子和同等性级别)做出决策时,做出的决策会受到预先设想答案的影响。而当成本变得非常昂贵时,而进行一些复制似乎有点太过。
将数据库的规模调整看作一个设计过程,需要意识它是迭代的,并且支持需求和使用的发现和研究。与任何设计过程一样,最佳规模在经济上和操纵上都不理想,其投入的时间和资源也很有限。
在设计过程的简朴性和成本与正确性之间存在内在的权衡。毕竟,复杂的模子可以更好地预测数据库性能,但其成本可能与构建数据库自己一样高,而且需要太多的输入,以至于其应用不切实际。
带来哪些题目?
数据库的工作负载通常被描述为查询的吞吐量和它必须支持的数据集大小。这将会引发出一些题目:
这个数字是最大吞吐量照旧平均值?(工作量通常有周期性变化和峰值)
是否应该分离某些类型的查询?(比方读取和写入)
如果还没有使用这个数据库,那么如何估计查询的数量?数据集多大?
热门数据集是什么?数据库存储的数据比它们在任何给定时间点所能提供的数据多得多。
数据模子呢?从经验中知道,数据模子对查询数量、性能和存储大小有很大的影响。
预期增长是多少?盼望构建一个可以随工作负载扩展的数据库。
查询的服务品级目标(SLO)是什么?设计的延迟目标是什么?
有些人很幸运,拥有一个可以正常工作的系统,也许另有运行正常的数据库,他们可以很容易地从中提取或推断出这些题目的答案。但在通常环境下,一些被迫使用推测方法和粗略盘算。这并不像听起来那么糟糕。比方,可以了解这个使用蒙特卡罗工具的模子,如下图所示。
估计工作量很有趣。但是出于简朴的分级目的,人们感爱好的是吞吐量和数据集的最大值,并将只区分两种查询:读取和写入,其原因将在背面解释。
还可以假设对数据模子的控制水平很高。对于任何NoSQL数据库,其目标是通过一个查询来处理所有需要的数据--如果用户想优化读取或写入,需要做出决定。
通常的基本步调是:
(1)估计峰值数据集大小和工作负载。
(2)开端绘制数据模子,对优化目标进行高层决策。
(3)根据数据模子估计读/写比率。
构建数据库的性能模子
数据库的性能模子必须在一些有时相互冲突的需求之间取得均衡,它必须考虑充足的性能和容量安全裕度,但需要在成本、可靠性与性能、连续和峰值负载之间实现均衡,但仍旧可以简化,即使在没有特定应用步伐的环境下使用,同时提供明确的结果。
这确实是一项具有挑战性的任务。但它可以简朴得多。比方,以下是它如何与Scylla一起工作,Scylla是一个兼容Apache Cassandra的开源NoSQL数据库。
查询vs操纵
工作负载是根据查询(通常是CQL)指定的。CQL查询可能非常复杂,并生成数量不一的基本操纵,这些操纵的性能相对可预测。以下面的CQL查询为例:
1.SELECT * FROM user_stats WHERE id=UUID
2.SELECT * FROM user_stats WHERE username=USERNAME
3.SELECT * FROM user_stats WHERE city=”New York” ALLOW FILTERING
查询#1将使用主键定位记载,因此会立即在正确的分区上实行--根据同等性级别,它仍旧可能分解为几个操纵,由于将查询多个副本(稍后详细先容)。
只管查询#2看起来非常相似,但它会基于二级索引查找记载,分解为两个子查询:一个子查询到全局二级索引以查找主键,另一个子查询从分区检索行。此外,根据同等性级别,这可能会生成多个操纵。
查询#3甚至更极端,由于它跨分区扫描;它的体现将是糟糕的和不可预测的。
另一个例子是UPDATE查询:
1.UPDATE user_stats SET username=USERNAME, rank=231, score=3432 WHERE ID=UUID
2.UPDATE user_stats SET username=USERNAME, rand=231, score=3432 WHERE ID=UUID IF EXISTS
查询#1可能会直观地分解为读取和写入这两个操纵--但在CQL UPDATE查询中是UPSERT查询,只会生成1个写入操纵。
然而,查询#2只管看起来很相似,但它不仅要求在所有副本上先读取后写入,而且还要求进行编排的轻量级事件(LWT)。
类似地,查询生成的磁盘操纵数量可能会有很大的不同。大多数数据库在排序字符串表(SSTable)存储格式中使用日志结构的归并树(LSM)结构。他们从不修改磁盘上的SSTable文件,它们是不变的。写入被持久化到只追加提交日志以进行规复,并写入内存缓冲区(memtable)。当memtable变得太大时,它会被写入磁盘上的一个新的SSTable文件,并从内存中刷新。这使得写路径非常高效,但在读取时引入了一个题目:数据库必须在多个SSTables中搜索一个值。
为了防止这种读取失控放大,数据库定期将多个SSTables压缩到数量更少的文件中,只保存最新的数据。这淘汰了完成查询所需的读取操纵数量。
这意味着将磁盘操纵归因于单个查询实际上是不可能的。磁盘操纵的确切数量取决于SSTables的数量、它们的分列、压缩策略等。开源的NoSQL数据库旨在最大限度地使用存储空间,所以只要磁盘速度充足快并且不是瓶颈,就可以忽略这个维度。这就是推荐快速本地NVMe磁盘的原因。
虽然这个示例告急关注CQL,但预测查询成本并不是唯一的题目。事实上,查询语言越丰富、功能越强盛,就越难以预测其性能。比方,由于SQL的强盛功能,它的性能可能难以预测。因此,复杂的查询优化器是RDBMS的告急组成部分,它确实提高了性能,但其代价是使性能更加难以预测。这是NoSQL采取的另一种折衷方法:颖呷考虑可预测的性能和可扩展性,而不是功能丰富的查询语言。
同等性的难题
分布式可用数据库需要可靠地将数据复制到多个节点。每个键空间可以配副本的数量,称之为复制因子。从客户端的角度来看,这可以同步发生,也可以异步发生,这取决于写入的同等性级别。
比方,当同等性级别为1时,数据终极会写入所有副本,但客户端只会等待一个副本确认写入。即使有些节点暂时不可用,数据库也应该在这些节点可用时缓存写入和复制(这称为暗示切换)。在实践中,可以假设每个写查询都会在每个副本上生成至少一个写入操纵。
然而,对于读取来说,环境有些不同。同等性级别为ALL的查询必须从所有副本读取数据,从而生成与集群的复制因子一样多的读取操纵,但同等性级别为1的查询只从单个节点读取数据,从而实现更自制、更快的读取。这允许用户以捐躯同等性为代价从集群中挤出更多的读吞吐量,由于一个节点在被读取时可能还没有获得近来的更新。
最后,另有轻量级事件(LWT)需要考虑。如上所述,轻量级事件(LWT)需要使用Paxos算法对所有副本进行编排。轻量级事件(LWT)不仅强制每个副本读取然后写入该值,而且还需要维护事件的状态,直到Paxos轮竣事。由于轻量级事件(LWT)的行为方式不同,需要将其视为每个核心能够支持的吞吐量的独立性能模子。
所有操纵都是平等的,但有些操纵比其他操纵更平等
如今已经将CQL查询分解为基本操纵,那么可以询问一些题目:每个操纵需要多少时间(和资源)?CPU核心能支持的容量是多少?同样,其答案有点复杂。作为一个例子,可以考虑一个简朴的读取并遵循节点中的实行步调:
(1)在内存表中查找值。
(2)在缓存中查找值。
(3)在SSTables中逐层查找值并归并值。
(4)响应协调者。
显然,如果它们在基于内存的memtable或行级缓存中,读取将更快地完成,这没什么好奇怪的。此外,步调#3和#4可能会产生更高的成本,这取决于从磁盘读取、处理和通过网络传输的数据的大小。如果需要读取10MB的数据,这可能需要相称长的时间。这可能是由于存储在单个单位格中的值很大,也可能是由于范围扫描返回许多结果。但是,在值很大的环境下,Scylla不能将它们分成更小的块,必须将整个单位格加载到内存中。
一般来说,建议对数据进行建模,使分区、行和单位不要增长得太大,以确保淘汰性能的差别。然而在现实中,总会有一些差别。
当涉及到分区访问模式时,这种差别尤其明显。许多数据库用于跨节点扩展和传播数据的策略是将数据分块到彼此独立的分区中。但独立也意味着分区可能会经历不均匀的负载,导致所谓的“热分区”题目,即单个分区会遇到节点容量限制,只管集群的其余部分有充足的资源。这个题目的发生在很大水平上取决于数据模子、数据集中分区键的分布以及工作负载中键的分布。由于预测热分区通常需要分析整个数据集和工作负载,因此在设计阶段是不切实际的,因此可以提供某些已知的分布作为模子的输入,或者对分区的相对热进行一些假设。别的,nodetool toppartitions命令可以帮助定位热分区。
物化视图、二级索引和其他
数据库具有自动二级索引和物化视图以及变更数据捕获(CDC)功能。这些表本质上是由数据库自己维护的辅助表,只允许使用一个写操纵以多种情势写入数据。
而在幕后,Scylla根据用户提供的模式派生要写入的新值,并将这些新值写入不同的表。在这方面,Scylla和RDBMS之间的告急区别在于,派生数据是异步写入的,并且作为索引和物化视图跨网络写入,而不范围于单个分区。这是另一个需要考虑的写入的来源。在Scylla中,它是可以预测的,并且在性能上与用户生成的操纵相似。对于写入的每一项,CDC和从写入单位格派生的每个物化视图或二级索引都将触发一次写入。在某些环境下,物化视图和CDC可能需要额外的读取,比方,如果启用了CDC的“预映像”功能,就会发生这种环境。别的需要记住的是,一个CQL查询可以触发多个写操纵。
CDC、二级索引和物化视图被实现为由Scylla自己管理的常规表,但这也意味着它们消耗的磁盘空间与用户表相称,因此必须在容量计划中考虑到这一点。
高峰和数据库维护
所有数据库都需要实行各种维护操纵。RDBMS需要清理重做日志(比方Postgre SQL VACUUM),转储快照到磁盘(查看Redis),或实行内存垃圾收集(Cassandra、Elastic、HBase)。使用LSM存储的数据库(如Cassandra、HBase、Scylla)也需要定期压缩SSTables,并将memtable刷新到新的SSTables中。
如果数据库充足智能,可以将压缩和修复等维护操纵推迟到稍后、负载更少的时间,那么可以在短时间内获得最佳性能。然而,终极将不得不为这些维护操纵预留资源。这对于大多数系统来说非常有用,由于一天内的负载的分配并不是均匀的。但是,企业的计划应该为数据库的连续长期操纵提供充足的容量。
此外,仅根据吞吐量进行规划是不敷的。在某种水平上,可以使数据库过载以获得更高的吞吐量,但其代价是更高的延迟。
在这个意义上,基准往往具有误导性,通常时间太短而无法达到有意义的连续运行。延迟/吞吐量的权衡通常更容易度量,甚至在较短的基准测试中也可以观察到。
另一个告急但常常被忽视的题目是降级操纵。作为一个本地冗余和高可用的数据库,Scylla被设计为平滑地处理节点故障(根据用户设置的同等性约束)。但是,虽然故障在语义上是同等的,但这并不意味着它们是动态透明的,而其容量的明显损失将影响集群的性能,以及故障节点的规复或替换。在调整集群规模时也需要考虑这些因素。
选择适当规模的节点
由于Scylla的容量可以通过增长节点或选择更大的节点来增长,一个有趣的题目出现了:应该选择哪种扩展策略?一方面,更大的节点效率更高,由于可以独立于服务Scylla的内核分配CPU核来处理网络和其他任务,并淘汰节点协调的相对开销。另一方面,拥有的节点越多,当其中一个节点出现故障时,损失的部分容量就越少--只管丢失节点的概率轻微高一些。
对于非常大的工作负载,解决这个题目是没有意义的,由于大型节点是不敷的。但是对于许多较小的工作负载来说,3个中大型节点就充足了。这个决定与工作量相关。但是,对于可靠性降级操纵,建议至少运行6~9个节点(假设复制因子为3)。
结论
容量规划和调整集群规模可能非常复杂和具有挑战性。本文已经讨论了如何考虑安全裕度、维护操纵和使用模式。告急的是要记住,任何推测都只是迭代的初始估计。一旦投入生产并有了真实的数据,可以让它指导实行容量计划。(陶然) |