Parquet中的索引

51

我希望能够对Parquet表执行快速范围查询。与总大小相比,需要返回的数据量非常小,但因为必须执行完整列扫描,所以对于我的使用情况来说太慢了。

使用索引可以解决这个问题,我读到Parquet 2.0中将添加此功能。然而,我找不到任何其他有关此功能的信息,因此我猜想它没有被添加。如果数据被排序(在我的情况下是这样的),我不认为会有任何根本性的障碍阻止添加(多列)索引。

我的问题是:Parquet什么时候会添加索引,如何进行高级别设计?我想,如果索引指向正确的分区,我已经很满意。

此致敬礼,

Sjoerd。


很长一段时间。已经计划在 v2.0 中实现。 - user568109
也许对你有兴趣:https://github.com/lightcopy/parquet-index - Aydin K.
https://blog.cloudera.com/speeding-up-select-queries-with-parquet-page-indexes/ - akki
2个回答

55

更新于2018年12月:

Parquet格式版本2.5添加了列索引。

https://github.com/apache/parquet-format/blob/master/CHANGES.md#version-250

请参阅https://issues.apache.org/jira/browse/PARQUET-1201以获取该新功能的子任务列表。

请注意,此功能刚刚合并到Parquet格式本身中,不同的后端(Spark、Hive、Impala等)开始支持它需要一些时间。

这个新功能称为列索引。基本上,Parquet在parquet布局中添加了两个新结构 - 列索引和偏移索引。

下面是更详细的技术解释,说明它解决了什么问题以及如何解决

问题陈述

在当前格式中,ColumnMetaData中存储了ColumnChunks的统计信息,并且在DataPageHeader结构体中存储了单个页面的统计信息。读取页面时,读取器必须处理页面头以确定是否可以根据统计信息跳过页面。这意味着读取器必须访问列中的所有页面,因此可能会从磁盘读取大部分列数据。

目标

通过允许基于它们的最小值和最大值直接访问页面,使范围扫描和点查找都具有I/O效率。特别是:

  1. 基于行组的排序列进行单行查找只需要读取每个检索列的一个数据页。基于排序列的范围扫描只需要读取包含相关数据的确切数据页。
  2. 使其他选择性扫描的I/O效率高:如果我们在非排序列上有一个非常选择性的谓词,对于其他检索列,我们只需要访问包含匹配行的数据页。
  3. 没有额外的解码工作用于没有选择性谓词的扫描,例如全行组扫描。如果读取器确定它不需要读取索引数据,则不会产生任何开销。
  4. 排序列的索引页面通过仅存储页面之间的边界元素来使用最小存储空间。

非目标

支持相当于二级索引的内容,即按键值对非排序数据进行排序的索引结构。

技术方法

我们向行组元数据添加了两个新的按列结构: ColumnIndex: 这允许基于列值导航到一列的页面,并用于定位包含符合扫描谓词的匹配值的数据页。 OffsetIndex: 这允许通过行索引导航,并用于检索通过ColumnIndex标识为匹配的行的值。一旦跳过一列的行,就必须跳过其他列中相应的行。因此,存储在RowGroup中每个列的OffsetIndexes被一起存储。
这些新的索引结构与RowGroup分开存储,靠近页脚,以便读取器不必支付I/O和反序列化成本来读取它们,如果它不进行选择性扫描。索引结构的位置和长度存储在ColumnChunk和RowGroup中。
Cloudera的Impala团队对这个新功能进行了一些测试(尚未作为Apache Impala核心产品的一部分提供)。这是他们的性能改进:

HDFS I/O in Bytes

Scanner CPU time in ms

我可以看到一些查询在CPU时间和需要从磁盘读取的数据量方面都有很大的改善。

2016年的原始回答:

struct IndexPageHeader {
  /** TODO: **/
}

https://github.com/apache/parquet-format/blob/6e5b78d6d23b9730e19b78dceb9aac6166d528b8/src/main/thrift/parquet.thrift#L505

目前尚未实现首页标头。

请参考上面的Parquet格式源代码。 甚至在当前的Parquet 2.0中也没有看到它。

但是,Ryan Blue在Parquet方面的回答非常好,它具有伪索引功能(布隆过滤器)。

如果您对更多细节感兴趣,我建议阅读关于Parquet布隆过滤器和谓词下推如何工作的优秀文档https://www.slideshare.net/RyanBlue3/parquet-performance-tuning-the-missing-guide以及一个更技术的、针对实现的文档https://homepages.cwi.nl/~boncz/msc/2018-BoudewijnBraams.pdf


2
依旧不变:https://github.com/apache/parquet-format/blob/952c26375eb15c6a27a770f26a6292264b1b7328/src/main/thrift/parquet.thrift#L505 - Javier
Tagar,我还没有在Parquet中看到任何布隆过滤器。你能否详细解释一下你的答案或者提供一个参考来源? - Paul-Armand Verhaegen
@Paul-ArmandVerhaegen 布隆过滤器一直存在,通常无需进行任何操作即可启用。我为您添加了一些参考资料。 - Tagar
@Tagar,感谢您提供的优秀阅读材料,但我还是有些困惑。据我所知,您提到了一个使用parquet存储格式实现布隆过滤器的框架。我们以前也用过这个,例如,为了知道一个guid是否有很高的概率在一个parquet文件中被找到而不必读取整个parquet文件。如果需要,这可以存储在parquet文件的元数据中,但并不是parquet本身支持布隆过滤器的功能。请参见https://issues.apache.org/jira/browse/PARQUET-41,了解正在进行的工作。 - Paul-Armand Verhaegen
是的,列索引仅在两个月前提交到Parquet格式。 Parquet读取器依赖于实现,正如我在上面的答案中提到的那样,“请注意,此功能刚刚合并到Parquet格式本身中,不同的后端(Spark,Hive,Impala等)需要一些时间才能开始支持它。” - Tagar

42

Parquet目前为每个数据页保留最小值和最大值统计信息。一个数据页是单列的约1MB值(编码后)的分组;多个页面组成了Parquet的列块

这些最小值和最大值用于过滤列块和构成块的页面。因此,您可以通过按要过滤的列对记录进行排序,然后将数据写入Parquet来改善查询时间。这样,您可以充分利用统计信息过滤功能。

您还可以通过减少页面和行组大小来使用此技术进行更细粒度的过滤,但这样会牺牲编码效率和I/O效率。


1
+1 很棒的回答。不过我有个问题。“通过减小页面和行组大小,您还可以使用此技术获得更细粒度的过滤”。- 您是指mapred.max.split.size还是其他什么? - Tagar
1
我指的是两个Parquet设置:parquet.block.size(目标行组大小,以字节为单位,默认为128MB)和parquet.page.size(压缩前但编码后的目标页面大小,以字节为单位,默认为1MB)。 - blue
有趣。谢谢。这不会像使用Cassandra那样快,而我现在正在使用它,但应该会有很大的改进。我会在有空的时候尝试一下。 - Sjoerd van Hagen
链接“Parquet的列块”已经失效... - Idonknow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接