为什么 Spark SQL 认为索引的支持不重要?

Why Spark SQL considers the support of indexes unimportant?

引用 Spark 数据帧,Datasets and SQL manual:

A handful of Hive optimizations are not yet included in Spark. Some of these (such as indexes) are less important due to Spark SQL’s in-memory computational model. Others are slotted for future releases of Spark SQL.

作为 Spark 的新手,我对此感到有点困惑,原因有二:

  1. Spark SQL 旨在处理大数据,至少在我的使用中 如果数据大小远远超过可用内存的大小。 假设这并不少见,“Spark SQL's 是什么意思 内存计算模型”?Spark SQL 是否仅推荐用于 数据适合内存的情况?

  2. 即使假设数据适合内存,对非常大的数据进行全面扫描 数据集可能需要很长时间。我读 this argument 反对 在内存数据库中建立索引,但我不相信。这个例子 那里讨论了对 10,000,000 条记录的扫描 table,但事实并非如此 真正的大数据。扫描具有数十亿条记录的 table 可能会导致 "SELECT x WHERE y=z" 类型的简单查询需要永远 立即返回。

我知道索引有一些缺点,比如速度较慢 INSERT/UPDATE、space 要求等。但是在我的用例中,我首先处理大量数据并将其加载到 Spark SQL,然后将这些数据作为一个整体进行探索,无需进一步修改。 Spark SQL 对于数据的初始分布式处理和加载很有用,但缺乏索引使得交互式探索比我预期的更慢、更麻烦。

我想知道为什么 Spark SQL 团队认为索引在他们的路线图之外的程度不重要。是否有不同的使用模式可以提供索引的好处,而无需诉诸独立实现等效的东西?

索引输入数据

  • 外部数据源的索引不在Spark范围内的根本原因是Spark不是一个数据管理系统,而是一个批处理数据处理引擎。由于它不拥有所使用的数据,因此无法可靠地监控变化,因此无法维护索引。
  • 如果数据源支持索引,Spark 可以通过谓词下推等机制间接使用它。

索引分布式数据结构:

  • 标准索引技术需要持久且定义明确的数据分布,但 Spark 中的数据通常是短暂的,其确切分布是不确定的。
  • 通过适当的分区结合列式存储和压缩实现的高级数据布局可以提供非常高效的分布式访问,而无需创建、存储和维护的开销indices.This是不同的内存中列式存储使用的常见模式系统。

也就是说,某些形式的索引结构确实存在于 Spark 生态系统中。最著名的是 Databricks 在其平台上提供 Data Skipping Index

其他项目,如 Succinct(目前大部分不活跃)采用不同的方法并使用具有随机访问支持的高级压缩技术。

当然,这提出了一个问题——如果您需要高效的随机访问,为什么不使用从一开始就设计为数据库的系统。那里有很多选择,包括至少一些由 Apache 基金会维护的选择。同时 Spark 随着项目的发展,您使用的引用可能无法完全反映未来的 Spark 方向。

一般来说,索引的效用充其量是值得怀疑的。相反,数据分区更为重要。它们是非常不同的东西,仅仅因为你选择的数据库支持索引并不意味着它们在 Spark 试图做的事情上有意义。而且和"in memory".

没有任何关系

那么什么是索引呢?

在永久存储非常昂贵(而不是基本上免费)的日子里,关系数据库系统都是为了最大限度地减少永久存储的使用。关系模型必然会将记录分成多个部分——规范化数据——并将它们存储在不同的位置。要读取客户记录,也许您读取 customer table、customerType table,从 address [=69= 中取出几个条目], 等等。如果你有一个解决方案需要你阅读整个 table 来找到你想要的东西,这是非常昂贵的,因为你必须扫描这么多 tables.

但这不是做事的唯一方法。如果不需要固定宽度的列,则可以将整组数据存储在一个地方。无需对一堆 table 进行全面 table 扫描,只需对单个 table 进行扫描即可。这并不像您想象的那么糟糕,尤其是如果您可以对数据进行分区的话。

40 年后,物理定律发生了变化。硬盘驱动器随机 read/write 速度和线性 read/write 速度已经大相径庭。基本上每个磁盘每秒可以进行 350 次磁头运动。 (多一点或少一点,但这是一个很好的平均数字。)另一方面,单个磁盘驱动器每秒可以读取大约 100 MB。这是什么意思?

算一算并想一想——这意味着 如果每次磁盘磁头移动读取的数据少于 300KB,您就会限制驱动器的吞吐量

说真的。想一想。

索引的目的是让您可以将磁头移动到您想要的磁盘上的精确位置,然后只读取该记录——比如说 address 记录作为您的 [= 的一部分加入10=] 记录。我说,那没用。

如果我根据现代物理学设计一个索引,它只需要让我在目标数据块的 100KB 左右范围内(假设我的数据被分成大块——但我们无论如何在这里谈论理论)。根据上面的数字,任何比这更高的精度都是浪费。

现在回到您的规范化 table 设计。假设一条 customer 记录实际上分为 6 行,保存在 5 table 秒内。总共 6 次磁盘磁头移动(我假设索引缓存在内存中,因此没有磁盘移动)。这意味着我可以读取 1.8 MB 的线性/非规范化客户记录并且同样高效。

那么客户历史呢?假设我不只是想看看客户今天的样子——想象一下我想要完整的历史记录,还是历史记录的一个子集?将以上所有内容乘以 10 或 20 即可得到图片。

比索引更好的是数据分区——确保所有客户记录最终都在一个分区中。这样只要移动一个磁头,我就可以读取整个客户历史记录。一盘磁头移动。

再说一遍你为什么需要索引。

索引 vs ___ ?

不要误会我的意思——"pre-cooking" 您的搜索是有价值的。但物理定律提出了一种比传统指数更好的方法。与其将客户记录存储在一个位置,并创建一个指向它的指针——一个索引——为什么不将记录存储在多个位置?

请记住,磁盘 space 基本上是免费的。与其尝试最小化我们使用的存储量(关系模型的过时产物),不如将您的磁盘用作搜索缓存。

如果您认为有人希望看到按地理位置和按销售代表列出的客户,请以优化这些搜索的方式存储您的客户记录的多个副本。就像我说的,像使用内存缓存一样使用磁盘。与其通过将不同的持久数据片段放在一起来构建内存缓存,不如构建持久数据以镜像内存缓存,这样您只需读取它即可。事实上,甚至不必费心尝试将其存储在内存中——每次需要时直接从磁盘读取即可。

如果您认为这听起来很疯狂,请考虑这一点——如果您将它缓存在内存中,您可能会缓存它两次。您的 OS / 驱动器控制器可能使用主内存作为缓存。不要为缓存数据而烦恼,因为其他人已经缓存​​了!

但我离题了...

长话短说,Spark 绝对支持正确的索引类型——能够从原始数据创建复杂的派生数据,从而使未来的使用更加高效。它只是没有按照您想要的方式进行。