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

58

引用 Spark DataFrames, 数据集和SQL手册:

有一些 Hive 优化措施尚未包含在 Spark 中。其中一些(例如索引)由于 Spark SQL 的内存计算模型而不那么重要。其他优化措施将在未来的 Spark SQL 版本中推出。

作为一个新手,我对此感到困惑,原因有两个:

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

  2. 即使假设数据适合内存,对非常大的数据集进行完全扫描可能需要很长时间。我阅读了反对在内存数据库中建立索引的论点,但我并没有被说服。那里的例子讨论了一张1000万条记录的表的扫描,但那并不是真正的大数据。扫描数十亿条记录的表可能会导致“SELECT x WHERE y=z”类型的简单查询无法立即返回,而是需要花费很长时间。

我知道索引存在一些缺点,如插入/更新速度较慢、空间需求等。但在我的使用情况下,我首先处理并加载大量数据到Spark SQL中,然后将这些数据作为整体进行探索,不进行进一步修改。Spark SQL对于初始分布式处理和数据加载非常有用,但缺乏索引使得交互式探索比我预期的更加缓慢和繁琐。
我想知道为什么Spark SQL团队认为索引不重要以至于没有将其列入路线图。是否有不同的使用模式可以提供索引的好处,而不必独立实现类似的东西?

2
Spark不是数据库。那么你所说的索引是什么意思?你可以在Spark中编写一个进程来索引原始文本文件。您可以将原始文本文件保存为Parquet文件,并按需要对数据进行分区。您还希望它为您做些什么? - David Griffin
2
@DavidGriffin 所引用的部分是关于Hive优化的。 Spark SQL将在Hive表上执行。因此,Hive索引提供的优化并不适用于SparkSQL。这就是它的含义,而不是Spark本身就是一个数据库。 - OneCricketeer
4
Spark SQL提供了SQL抽象层。不考虑实现细节和存储机制,我认为可以合理地期望Spark SQL提供与sql CREATE INDEX语句等效的功能,而无需自己实现它。 - hillel
1
为什么这是合理的?您能在Spark中创建Hive表,然后转到Hive并在表上创建索引吗?如果可以,为什么需要由Spark负责?我无法在Spark中创建Kafka主题或添加/删除Kafka主题的分区。而且,期望我能够这样做是不合理的。 - David Griffin
8
注意,我在回复中并没有提到Hive,而且我认为它并不重要。我的意思是说,总体而言,索引可以节省很多时间,并且对于加速SQL查询非常有用。从我引用的文章中可以看出,Spark SQL团队认为索引并不重要,因此没有实现。作为一个Spark/BigData新手,我想了解原因。我希望得到类似这样的答案:“由于Spark架构的限制,实现起来很困难”,“索引对于典型的Spark SQL应用程序来说不太有用”,或者“索引已经被其他更适合的工具覆盖了”。 - hillel
2
Spark可以与多种数据存储格式一起使用,其中一些支持索引,而另一些则不支持。Spark + Postgres允许您使用Postgres索引。但是,Spark + Parquet文件无法使用索引,因为Parquet湖不支持索引。 - Powers
2个回答

53

索引输入数据

  • 索引外部数据源的根本原因是 Spark 不是数据管理系统而是批处理数据处理引擎。由于它不拥有正在使用的数据,因此无法可靠地监视更改,并且因此无法维护索引。
  • 如果数据源支持索引,则可以通过谓词下推这样的机制间接地被 Spark 利用。

索引分布式数据结构:

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

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

其他项目,比如Succinct(目前大部分处于停滞状态),采用不同的方法,使用高级压缩技术和随机访问支持。

当然,这引发了一个问题 - 如果您需要有效的随机访问,为什么不使用从一开始就设计为数据库的系统。有许多选择,包括至少由 Apache Foundation 维护的一些选择。同时,作为一个项目,Spark 在不断发展,所以您引用的语录可能无法完全反映 Spark 未来的方向。


1
@zero323:关于“由于它不拥有正在使用的数据,因此无法可靠地监视更改”的说法。这是否与Spark支持分区的事实相矛盾? - shridharama
@shridharama 在这个上下文中,分区是什么意思? - zero323
@zero323 我指的是Spark支持的函数,比如repartition(),以及Spark能够发现分区的Parquet数据的能力。 - shridharama
2
如果是这种情况,答案是否定的。repartition 不会监控任何内容。它创建的是不可变的、有效的临时数据结构,仅限于给定的应用程序。分区发现只是假设该结构是有效的。它不会监视数据,也不会超出标准 Parquet 的能力进行验证。 - zero323
@zero323 桶分配意味着数据不一定限于应用程序,对吗? - thebluephantom

20

总的来说,索引的效用至多是有疑问的。相比之下,数据分区更为重要。二者截然不同,只是因为你选择的数据库支持索引并不意味着它们符合Spark的目标。这与“内存”无关。

那么索引到底是什么?

在永久存储非常昂贵(而不是现在基本上是免费的)的年代里,关系型数据库系统主要是通过尽量减少永久存储的使用来实现的。由于必要性,关系模型将记录拆分为多个部分(规范化数据),并在不同位置存储它们。为了读取客户记录,也许您需要读取一个customer 表、一个customerType 表,从一个 address 表中获取一些条目等等。如果您的解决方案要求您读取整个表才能找到所需内容,这将非常昂贵,因为您必须扫描这么多表。

但这不是做事情的唯一方式。如果您不需要固定宽度的列,您可以将整个数据集存储在一个地方。而不是在一堆表上进行全表扫描,您只需要在单个表上进行即可。这并不像您想象的那样糟糕,尤其是如果您可以对数据进行分区。

40年后,物理定律已经改变了。硬盘随机读/写速度和线性读/写速度已经大幅分化。您基本上可以在每个磁盘上执行350次头部移动操作。 (略有多或少,但这是一个好的平均数。)另一方面,单个磁盘驱动器每秒可以读取约100 MB。这意味着什么呢?

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

认真思考一下这个问题。

索引的目标是让您将磁盘头移动到所需的磁盘位置,然后只读取该记录 - 例如仅作为您的客户记录的一部分加入的“地址”记录。但我认为,这是无用的。
如果我基于现代物理设计索引,它只需要将我带到目标数据附近100KB左右的位置(假设我的数据被布置在大块中 - 但无论如何,我们都在谈论理论)。根据上面的数字,任何比那更精确的都是浪费。
现在回到规范化的表设计。假设一个“客户”记录实际上跨越了5个表中的6行。总共需要6次磁盘头移动(我假设索引已缓存在内存中,因此没有磁盘移动)。这意味着我可以阅读1.8 MB的线性/非规范化客户记录,并且效率相同。
那么客户历史记录呢?假设我不仅想查看客户今天的样子 - 想象一下我想要完整的历史记录或历史记录的子集?将上面的所有内容乘以10或20,你就有了画面。
比索引更好的东西是数据分割-确保所有客户记录都在一个分区中。这样,只需一次磁盘头移动,我就可以读取整个客户历史记录。一次磁盘头移动。
再告诉我为什么您需要索引。
索引vs___?
别误会 - “预制”搜索确实有价值。但是物理定律建议以比传统索引更好的方式进行。而不是将客户记录存储在一个精确位置并创建指向该位置的指针 - 即索引 - 为什么不将记录存储在多个位置?
请记住,磁盘空间基本上是免费的。不要尝试最小化使用的存储量 - 这是关系模型的过时产物 - 只需将磁盘用作搜索缓存即可。

如果你认为有人想按地理位置和销售代表列出客户名单,那么请制作多份存储方式优化这些搜索的客户记录副本。就像我说的,将磁盘用作内存缓存。不要通过汇集不同的持久数据片段来构建内存缓存,而是构建持久数据以反映您的内存缓存,这样您只需读取即可。事实上,甚至不要费力地尝试将其存储在内存中——每次需要时直接从磁盘读取即可。

如果你觉得这听起来很疯狂,考虑一下-如果你将其缓存在内存中,你可能会缓存两次。很可能你的操作系统/驱动控制器使用主内存作为缓存。不要费心缓存数据,因为别人已经在缓存了!

但我偏离了主题...

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


2
@DavidGriffin,我理解你的观点,如果你的查询需要非常大量的磁头移动,那么读取顺序数据可能更好。但是要精确,我们应该考虑顺序读取的速度:如果数据的大小足够大,读取它所需的时间比磁头移动的时间更长,那么我们仍然会从索引中受益,不是吗?例如,简单的SELECT x WHERE y=z查询将需要很少的磁头移动,并且可能在非常大的数据大小(以太字节)上运行。在这些情况下,顺序读取是否会慢几个数量级? - hillel
1
你的查询结果返回一行还是成千上万行?如果是成千上万行,而且这些数据在整个数据库中分散不均,那么索引会带来更多的负面影响。如果只有一行数据,那么也许使用另外一个数据库更加合适(我从来没有在Spark中只处理单条记录,你呢?)。 - David Griffin
1
如果你仔细想一想,你在现实生活中从来不会只使用单行数据(没有相关联的行)。在测试中,是的。但在生产中不是这样的。数据本身是完全没有意义的,你的例子看起来很有说服力,直到你意识到它是一个无用的用例。 - David Griffin
1
谢谢@DavidGriffin,我想我理解了你的观点。我可能会接受zero323的答案,因为它涉及到这个问题的更多方面,但对我来说,这是一个非常有教育意义的讨论。 - hillel
2
40年后,物理定律已经改变。硬盘的随机读/写速度和线性读/写速度已经大幅分化。每个磁盘基本上可以进行350次头部移动。 (略多或略少,但这是一个很好的平均数。)另一方面,单个磁盘驱动器可以每秒读取约100 MB。应指出,对于SSD来说,这些数字差异巨大,这显着改变了完整表扫描与索引扫描计算。 - Joe Stevens
显示剩余6条评论

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