分区可以为分片系统提供许多好处,包括更快的查询执行。让我们来看看它是如何工作的。
在上一篇文章中,我描述了一个分片系统,以扩大查询和摄取工作负载的吞吐量和性能。在这篇文章中,我将介绍另一种常见的技术--分区,它为分片数据库的性能和管理提供进一步的优势。我还将介绍如何为查询和摄取工作负载有效地处理分区,以及如何管理冷(旧)分区,因为那里的读取要求与热(新)分区完全不同。
分片与分区
分片是一种在分布式数据库系统中分割数据的方式。每个分片中的数据不需要共享CPU或内存等资源,可以并行地读取或写入。
图1是一个分片数据库的例子。一个国家的50个州的销售数据被分成四个分片,每个分片包含12或13个州的数据。通过为每个分片指定一个查询节点,读取所有50个州的工作可以在这四个节点之间并行运行,与通过一个节点读取所有50个州的设置相比,执行速度将快四倍。关于分片及其对摄入和查询工作负载的扩展效果的更多信息可以在我之前的文章中找到:
图1:销售数据被分成四个分片,每个分片分配给一个查询节点。
分区是将每个分片内的数据分成不重叠的分区,以便进一步并行处理。这减少了对不必要的数据的读取,并允许有效地实施数据保留政策。
在图2中,每个分片的数据是按销售日划分的。如果我们需要创建一个特定日期的销售报告,如2022年5月1日,查询节点只需要读取其相应分区的2022.05.01的数据:
图2:每个分片的销售数据被进一步分割成不重叠的日分区。
这篇文章的其余部分将集中讨论分区的效果。我们将看到如何为热数据和冷数据的查询和摄取工作负载有效管理分区。
分区的效果
数据分区的三个最常见的好处是数据修剪、节点内并行性和快速删除。
数据修剪
一个数据库系统可能包含几年的数据,但大多数查询只需要读取最近的数据(例如,"过去三天有多少订单?")。如图2所示,将数据划分为不重叠的分区,可以很容易地跳过整个边界外的分区,只读取和处理相关的和非常小的数据集,以快速返回结果。
节点内的并行性
多线程处理和流式数据在数据库系统中至关重要,可以充分利用可用的CPU和内存,获得最佳性能。将数据划分为小的分区,使得实现多线程引擎更容易,每个分区执行一个线程。对于每个分区,可以生成更多的线程来处理该分区内的数据。了解分区的统计数据,如大小和行数,将有助于为特定分区分配最佳的CPU和内存数量。
快速删除数据
许多组织只保留最近的数据(例如,过去三个月的数据),并希望尽快删除旧数据。通过在不重叠的时间窗口上对数据进行分区,删除旧的分区就变得像删除文件一样简单,而不需要重新组织数据和中断其他查询或摄取活动。如果所有的数据都必须保留,本篇文章后面的部分将描述如何以不同的方式管理最近和旧的数据,以确保系统在所有情况下都能提供良好的性能。
存储和管理分区
为查询工作负载进行优化
一个分区已经包含了一个小的数据集,所以我们不想把一个分区存储在许多小的文件中(或者在内存数据库的情况下是大块)。一个分区应该只由一个或几个文件组成。
尽量减少分区中的文件数量有两个重要的好处。它既能减少读取数据执行查询时的I/O操作,又能改善数据编码/压缩。改进编码反过来又降低了存储成本,更重要的是,通过读取更少的数据来提高查询执行速度。
为摄取工作负载进行优化
Naive Ingestion为了将分区的数据保留在文件中,以获得上面提到的阅读优化的好处,每次摄取一组数据时,都必须对其进行解析并分割成正确的分区,然后将其合并到相应分区的现有文件中,如图3所示。
由于昂贵的I/O以及分区数据的混合和编码成本,将新数据与现有数据合并的过程往往需要时间。这将导致回应客户的数据被成功摄取的延迟,以及对新摄取的数据进行查询的延迟,因为它不会立即在存储中可用。
图3:新数据被立即合并到与现有数据相同的文件中的天真摄入。
低延迟的摄取。为了保持每次摄取的低延迟,我们可以把这个过程分成两个步骤:摄取和压缩。
摄取
在摄取步骤中,摄取的数据被分割并写入自己的文件中,如图4所示。它不会与分区的现有数据合并。一旦摄取的数据成功持久,摄取客户端将收到一个成功信号,新摄取的文件将可用于查询。
如果摄取率很高,许多小文件会在分区中积累,如图5所示。在这个阶段,需要某个分区数据的查询必须读取该分区的所有文件。当然,这对查询性能来说并不理想。下面描述的压缩步骤可以将这种文件的积累保持在最低限度。

图4:新摄入的数据被写入一个新文件。

图5:在一个高摄入量的工作负荷下,一个分区将积累许多文件。
压实
夯实是将一个分区的文件合并成一个或几个文件的过程,以提高查询性能和压缩。例如,图6显示分区2022.05.01的所有文件被合并成一个文件,分区2022.05.02的所有文件被合并成两个文件,每个文件都小于100MB。
对于不同的系统来说,关于压缩的频率和压缩文件的最大尺寸的决定将是不同的,但共同的目标是通过减少I/O(即文件的数量)和使文件大到足以有效压缩来保持查询的性能。

图6:将一个分区的几个文件压缩成一个或几个文件。
热分区与冷分区
经常被查询的分区被认为是热分区,而那些很少被读取的分区被称为冷分区。在数据库中,热分区通常是指包含近期数据的分区,如最近的销售日期。冷分区通常包含较老的数据,这些数据不太可能被读取。
此外,当数据变旧时,通常会以较大的块来查询,如按月甚至按年查询。下面是几个例子,可以毫不含糊地将数据从热到冷进行分类:
- 热:本周的数据。
- 不太热:前几周的数据,但在当前的月份。
- 寒冷:前几个月的数据,但在今年的数据。
- 更冷:去年和更早的数据。
为了减少热和冷数据之间的模糊性,我们需要找到两个问题的答案。首先,我们需要量化热、不那么热、冷、更冷,甚至可能是更多和更冷。第二,我们需要考虑在读取冷数据的情况下,如何实现更少的I/O。我们不希望为了得到去年的销售收入而读取365个文件,每个文件代表一天的数据分区。
分层分区
图7所示的分层分区为上述两个问题提供了答案。当前一周的每一天的数据都存储在自己的分区里。当月前几周的数据按周进行分区。当年前几个月的数据按月划分。甚至更早的数据也是按年划分的。
这种模式可以通过定义一个活动分区来代替当前日期分区而得到放松。所有在活动分区之后到达的数据将按日期分区,而活动分区之前的数据将按周、月和年分区。这使得系统可以根据需要保留尽可能多的小的最近分区。尽管本篇文章中的所有例子都是按时间来划分数据的,但只要你能为分区及其层次结构定义表达式,非时间分区也会有类似的效果:

图7:分层分区。
分层分区减少了系统中的分区数量,使其更容易管理,并减少了在查询较大和较早的数据块时需要读取的分区数量。
分层分区的查询过程与非分层分区的查询过程相同,因为它将应用相同的剪枝策略,只读取相关的分区。摄取和压缩过程会更复杂一些,因为在其定义的层次结构中组织分区会更困难。
聚合分区
许多组织不希望保留旧的数据,而是倾向于保留聚合数据,如每个月的订单数量和每个产品的总销售额。这可以通过汇总数据和按月分区来支持。然而,由于聚合分区存储的是聚合数据,它们的模式将不同于非聚合分区,这将导致摄入和查询的额外工作。有不同的方法来管理这种冷的和聚合的数据,但它们是适合于未来文章的大主题。


