Pinot是进入Apache Incubation的开源的项目,可扩展的分布式OLAP数据存储。它是由LinkedIn开发的,可用于各种生产用例,以提供实时,低延迟的分析。
Pinot面临的最大挑战之一是在大型数据集的延迟和吞吐量上实现并维持严格的SLA。现有的索引技术(例如排序索引和反向索引)有助于加速文档搜索以提高查询延迟。但是,它们的性能与计算结果时要处理的文档数成线性比例。另一方面,对结果进行预汇总可以减少要处理的文档数量,从而对查询延迟实施恒定的上限。这可能导致有限的查询支持(通过限制维度组合)或存储空间爆炸(当考虑多个维度组合时)。
对于聚合和按组查询,我们引入了Star-Tree索引,以一种智能方式利用预聚合文档,从而实现了低查询延迟,同时又有效地利用了存储空间。
现有解决方案
考虑以下数据集作为讨论现有方法的示例(该数据集已按国家/地区进行重复数据删除和预分类):
国家 | 浏览器 | 语言环境 | 印象数 | |
0 | 认证机构 | 铬 | 恩 | 400 |
1个 | 认证机构 | 火狐浏览器 | fr | 200 |
2 | MX | 苹果浏览器 | es | 300 |
3 | MX | 苹果浏览器 | 恩 | 100 |
4 | 美国 | 铬 | 恩 | 600 |
5 | 美国 | 火狐浏览器 | es | 200 |
6 | 美国 | 火狐浏览器 | 恩 | 400 |
排序索引
在这种方法中,数据是按主键(在示例中为“国家/地区”)排序的,该键可能会在集合中的大多数查询中显示为过滤器。
以下是“国家/地区”列的排序索引:
文件编号 | |
认证机构 | 0-1 |
MX | 2-3 |
美国 | 4-6 |
考虑以下查询:表WHERE国家='美国'中的SELECT SUM(Impressions)
通过对“国家/地区”列进行排序索引,可以在文档中搜索给定的主键值“ USA”的时间复杂度从线性扫描Θ(n)减少为恒定时间值查找Θ(1)。另外,排序为文档提供了空间上的局部性,这在获取文档时大大减少了磁盘的获取,从而提高了延迟。
尽管与线性扫描相比,这是一个很好的改进,但是这种方法仍然存在一些问题:
- 排序只能应用于主键,这意味着只有对该列进行过滤的查询才能从排序索引中受益。
- 虽然搜索时间从Θ(n)减少到Θ(1),但是聚合成本仍然是要处理以回答查询的文档总数(在此示例中为3)的函数。
倒排索引
在这种方法中,对于给定列的每个值,我们维护一个显示此值的文档ID列表。
下面是示例数据集的“浏览器”和“语言环境”列的反向索引:
文件编号 | 语言环境 | 文件编号 | |
铬 | 0,4 | 恩 | 0,3,4,6 |
火狐浏览器 | 1,5,6 | es | 2,5 |
苹果浏览器 | 2,3 | fr | 1个 |
考虑以下查询:表WHERE Browser ='Firefox'中的SELECT SUM(Impressions)
通过在浏览器列上使用反向索引,类似于排序索引,文档搜索成为对键“ Firefox”的简单值查找,以直接获取[1、5、6]的匹配文档。
使用倒排索引可使任意列的文档搜索时间降低至恒定时间Θ(1)。但是,它无法利用空间局部性,并且类似于排序索引,聚合成本仍然是查询选择性的函数(在示例中,该值为3)。
预聚合
在此技术中,我们预先计算给定查询的答案。
在以下示例中,我们已经预先汇总了每个国家/地区的总展示次数:
印象数 | |
认证机构 | 600 |
MX | 400 |
美国 | 1200 |
考虑以下查询:表WHERE国家='美国'中的SELECT SUM(Impressions)
使用预聚合,仅通过值查找即可解决查询,我们可以直接获得1200的最终结果,而无需额外的聚合成本。
但是,使用示例中的预聚合,我们仅能解决以Country为谓词的查询。为了能够使用多个谓词回答查询,意味着对不同维度的各种组合进行了预聚合。这导致存储空间中的维数呈指数级增长(考虑查询:表WHERE国家='美国'AND浏览器='Firefox'AND语言环境='en'的SELECT SUM(Impressions))。
星树解决方案
下图显示了不同技术的性能提升和空间成本。在左侧,我们拥有索引技术,可在空间有限增加的情况下缩短搜索时间,但由于聚合成本,不能保证查询延迟的硬上限。在右侧,我们提供了预聚合技术,这些技术为查询延迟提供了硬上限,但遭受了存储空间指数爆炸的困扰。
我们提出了一种由星标纸(Xin,Han,Li和Wah,2003年)启发的Star-Tree数据结构,该结构在空间和延迟之间提供了可配置的折衷方案,并使我们能够实现查询延迟的硬上限。对于给定的用例。
在以下各节中,我们将首先定义Star-Tree数据结构,然后在示例数据集上定义一个Star-Tree示例。然后,我们将讨论如何在Pinot中利用Star-Tree来实现低延迟和高吞吐量。
树结构
Star-Tree是一种树数据结构,具有以下属性:
- 根节点(橙色):可以从中遍历树的其余部分的单个根节点。
- 叶子节点(蓝色):一个叶子节点最多可以包含T个文档,其中T是可配置的。
- 非叶节点(绿色):具有T个文档以上的节点进一步分为子节点。
- 星状节点(黄色):非叶节点也可以有一个特殊的子节点,称为星状节点。除去此级别的数据拆分维度后,此节点包含聚合的文档。星状节点可以是叶节点也可以是非叶节点。
- 维度拆分顺序([D1,D2]):树中给定级别的节点在特定维度的所有值上拆分为子节点。维度拆分顺序是维度的有序列表,用于确定树中给定级别要拆分的维度。
- 功能列对:生成树时要执行的预聚合。
- 最大叶子记录数:阈值T,用于确定是否进一步拆分每个节点。此阈值用于调整执行的预聚合级别。阈值越大,索引的大小就会越小,同时需要处理更多的文档来回答查询。
节点属性
每个节点中存储的属性如下:
- 维度:节点拆分所依据的维度。
- 值:节点表示的维的值。
- 开始/结束文档ID:此节点指向的文档范围。
- 汇总文档ID:一个单一文档,是此节点指向的所有文档的汇总结果。
索引生成
Star-Tree索引通过以下步骤生成:
- 首先根据DimensionsSplitOrder投影数据。仅保留拆分顺序中的尺寸,而其他尺寸则删除。对于保留尺寸的每个唯一组合,指标将按配置进行汇总。汇总的文档将被写入文件并用作初始的Star-Tree文档(与原始文档分离)。
- 根据DimensionsSplitOrder对Star-Tree文档进行排序。它首先在此列表中的第一个维度上排序,然后根据列表中其他维度的顺序在其余维度上排序。树中的每个节点都指向已排序文档中的一个范围。
- 可以递归创建树结构(从根节点开始),如下所示:
- 如果一个节点有多个T记录,则将其拆分为多个子节点,每个维度的值对应于树中当前级别的拆分顺序。
- 可以为当前节点创建一个Star-Node(按配置),方法是放下被拆分的维,然后对包含具有相同值的维的行的度量进行汇总。这些汇总的文档将附加到“ Star-Tree”文档的末尾。
- 如果当前维只有一个值,则将不会创建“星形节点”,因为“星形节点”下的文档与单个节点相同。
- 递归地重复上述步骤,直到没有更多节点可拆分为止。
- 可以基于不同的配置(dimensionsSplitOrder,aggregation,T)生成多个星型树。查询执行者可以选择具有能够解决查询的配置的查询(在“查询执行”部分中讨论)
聚合
聚合被配置为一对聚合函数和要应用聚合的列。
支持的函数支持
所有带有有限中间结果的聚合函数:
- COUNT:中间结果Long有界
- MIN:中间结果Double有界
- MAX:中间结果Double有界
- SUM:中间结果Double有界
- AVG:中间结果Pair <Double,Long>有界
- MINMAXRANGE:中间结果Pair <Double,Double>有界
- DISTINCTCOUNTHLL:中间结果HyperLogLog受限制
- PERCENTILEEST:中间结果QDigest有界
- PERCENTILETDIGEST:中间结果TDigest有界
不支持的功能
某些聚合函数具有不限大小的中间结果,不支持这些中间结果以防止存储空间爆炸。但是,可以使用上面支持的功能来实现结果的近似。
- DISTINCTCOUNT:中间结果集无界
- 百分号:中间结果列表无限制
查询执行
对于查询执行,其想法是首先检查元数据以确定是否可以使用Star-Tree文档解决查询。如果是这样,则遍历星空树以识别满足所有谓词的文档。在将遍历星空树的所有剩余谓词应用到已标识的文档之后,对合格的文档应用聚合/分组依据。
元数据检查
为了解决使用Star-Tree的聚合/分组查询,必须实现过滤器和group-by子句中的所有列(在DimensionsSplitOrder中配置),并且所有聚合都必须预先聚合(在functionColumnPairs中配置) )。查询执行者将选择符合要求的第一棵Star-Tree,如果没有人合格,则退回到常规聚合。
遍历树遍历树
的算法可以用下图描述:
应用其余谓词
由于叶子记录阈值,某些维可能无法拆分。在这种情况下,遍历树后将应用未拆分维度上的其余谓词(与常规查询执行相同,除了使用预聚合记录外)。叶子记录阈值是针对树中每个分支要处理的记录的上限。
例
国家 | 浏览器 | 语言环境 | 印象数 | |
0 | 认证机构 | 铬 | 恩 | 400 |
1个 | 认证机构 | 火狐浏览器 | fr | 200 |
2 | MX | 苹果浏览器 | es | 300 |
3 | MX | 苹果浏览器 | 恩 | 100 |
4 | 美国 | 铬 | 恩 | 600 |
5 | 美国 | 火狐浏览器 | es | 200 |
6 | 美国 | 火狐浏览器 | 恩 | 400 |
- 维度拆分顺序:[国家/地区,浏览器,区域设置]
- 功能列对:[SUM(曝光)]
- 最大叶子记录数:1(为清楚起见,我们在此处放了1,因此所有维度组合都已预先汇总)
树结构
括号中的值是该节点下所有文档的印象数的总和。
国家 | 浏览器 | 语言环境 | SUM_印象数 | |
0 | 认证机构 | 铬 | 恩 | 400 |
1个 | 认证机构 | 火狐浏览器 | fr | 200 |
2 | MX | 苹果浏览器 | 恩 | 100 |
3 | MX | 苹果浏览器 | es | 300 |
4 | 美国 | 铬 | 恩 | 600 |
5 | 美国 | 火狐浏览器 | 恩 | 400 |
6 | 美国 | 火狐浏览器 | es | 200 |
7 | 认证机构 | * | 恩 | 400 |
8 | 认证机构 | * | fr | 200 |
9 | 认证机构 | * | * | 600 |
10 | MX | 苹果浏览器 | * | 400 |
11 | 美国 | 火狐浏览器 | * | 600 |
12 | 美国 | * | 恩 | 1000 |
13 | 美国 | * | es | 200 |
14 | 美国 | * | * | 1200 |
15 | * | 铬 | 恩 | 1000 |
16 | * | 火狐浏览器 | 恩 | 400 |
17 | * | 火狐浏览器 | es | 200 |
18岁 | * | 火狐浏览器 | fr | 200 |
19 | * | 火狐浏览器 | * | 800 |
20 | * | 苹果浏览器 | 恩 | 100 |
21 | * | 苹果浏览器 | es | 300 |
22 | * | 苹果浏览器 | * | 400 |
23 | * | * | 恩 | 1500 |
24 | * | * | es | 500 |
25 | * | * | fr | 200 |
26 | * | * | * | 2200 |
因为在任何维度上都没有谓词或分组依据,所以请为所有维度选择“星形节点”(文档26)。与其在没有Star-Tree的情况下汇总所有七个文档,不如通过仅处理一个文档直接获得汇总结果2200。
从表WHERE的国家(地区)=“美国”中选择SELECT(总和)
因为在Country上只有一个谓词,所以选择Country值为'USA'的节点,为Browser和Locale选择星型节点(文档14)。通过仅处理一个文档,而不是过滤和汇总三个没有Star-Tree的文档,我们得到的汇总结果为1200。
从表WHERE Locale ='en'中选择SELECT SUM(Impressions)
与上一个查询类似,为“国家和浏览器”选择“星形节点”,为“语言环境”(文档23)选择值为“ en”的节点。同样,通过仅处理一个文档,我们得出的聚合结果为1500。
通过浏览器从表GROUP SELECT SUM(展示次数)
因为浏览器上有一个group-by子句,所以选择“国家和地区的星形节点”以及“浏览器的星形节点”之外的所有节点(文档15、19、22)。对于国家(*),浏览器(Chrome),由于没有区域设置的星形节点,因此请选择所有子节点。要获得分组结果,我们仅需要为每个组处理一个文档。
基准结果
我们已经进行了基准测试,比较了从Star-Tree索引和反向索引获得的性能提升与从TPC-H工具生成的4,800万条记录。结果如下:
通过对延迟和吞吐量进行如此巨大的改进,与没有索引技术的数据相比,Star-Tree索引仅花费大约12%的额外存储空间,而与具有反向索引的数据相比仅增加6%的存储空间。
有关更详细的基准测试结果,请参考本文的性能部分。
摘要
大多数索引技术可以帮助加速文档搜索,但是不能限制文档的处理需求。对于选择性低的查询,性能可能很差(选择的文档太多)。另一方面,预聚合可以限制查询延迟,但可能导致空间爆炸。
星空树索引是一种同时兼顾两个方面的技术。通过使用预聚合的文档和具有树结构的索引数据,Star-Tree可以加速文档搜索并绑定选定的文档,同时也不会占用空间。
Star-Tree索引极大地提高了低选择性查询的延迟和吞吐量,使同一硬件可以处理更多查询,从而显着降低了服务成本。