总的来说,索引的效用至多是有疑问的。相比之下,数据分区更为重要。二者截然不同,只是因为你选择的数据库支持索引并不意味着它们符合Spark的目标。这与“内存”无关。
那么索引到底是什么?
在永久存储非常昂贵(而不是现在基本上是免费的)的年代里,关系型数据库系统主要是通过尽量减少永久存储的使用来实现的。由于必要性,关系模型将记录拆分为多个部分(规范化数据),并在不同位置存储它们。为了读取客户记录,也许您需要读取一个customer
表、一个customerType
表,从一个 address
表中获取一些条目等等。如果您的解决方案要求您读取整个表才能找到所需内容,这将非常昂贵,因为您必须扫描这么多表。
但这不是做事情的唯一方式。如果您不需要固定宽度的列,您可以将整个数据集存储在一个地方。而不是在一堆表上进行全表扫描,您只需要在单个表上进行即可。这并不像您想象的那样糟糕,尤其是如果您可以对数据进行分区。
40年后,物理定律已经改变了。硬盘随机读/写速度和线性读/写速度已经大幅分化。您基本上可以在每个磁盘上执行350次头部移动操作。 (略有多或少,但这是一个好的平均数。)另一方面,单个磁盘驱动器每秒可以读取约100 MB。这意味着什么呢?
算一下然后想一想——这意味着如果您每个磁盘头移动读取的数据量少于300KB,则会限制驱动器的吞吐量。
认真思考一下这个问题。
索引的目标是让您将磁盘头移动到所需的磁盘位置,然后只读取该记录 - 例如仅作为您的客户记录的一部分加入的“地址”记录。但我认为,这是无用的。
如果我基于现代物理设计索引,它只需要将我带到目标数据附近100KB左右的位置(假设我的数据被布置在大块中 - 但无论如何,我们都在谈论理论)。根据上面的数字,任何比那更精确的都是浪费。
现在回到规范化的表设计。假设一个“客户”记录实际上跨越了5个表中的6行。总共需要6次磁盘头移动(我假设索引已缓存在内存中,因此没有磁盘移动)。这意味着我可以阅读1.8 MB的线性/非规范化客户记录,并且效率相同。
那么客户历史记录呢?假设我不仅想查看客户今天的样子 - 想象一下我想要完整的历史记录或历史记录的子集?将上面的所有内容乘以10或20,你就有了画面。
比索引更好的东西是数据分割-确保所有客户记录都在一个分区中。这样,只需一次磁盘头移动,我就可以读取整个客户历史记录。一次磁盘头移动。
再告诉我为什么您需要索引。
索引vs___?
别误会 - “预制”搜索确实有价值。但是物理定律建议以比传统索引更好的方式进行。而不是将客户记录存储在一个精确位置并创建指向该位置的指针 - 即索引 - 为什么不将记录存储在多个位置?
请记住,磁盘空间基本上是免费的。不要尝试最小化使用的存储量 - 这是关系模型的过时产物 - 只需将磁盘用作搜索缓存即可。
如果你认为有人想按地理位置和销售代表列出客户名单,那么请制作多份存储方式优化这些搜索的客户记录副本。就像我说的,将磁盘用作内存缓存。不要通过汇集不同的持久数据片段来构建内存缓存,而是构建持久数据以反映您的内存缓存,这样您只需读取即可。事实上,甚至不要费力地尝试将其存储在内存中——每次需要时直接从磁盘读取即可。
如果你觉得这听起来很疯狂,考虑一下-如果你将其缓存在内存中,你可能会缓存两次。很可能你的操作系统/驱动控制器使用主内存作为缓存。不要费心缓存数据,因为别人已经在缓存了!
但我偏离了主题...
长话短说,Spark绝对支持正确类型的索引-能够从原始数据创建复杂的派生数据,以使未来的使用更加高效。它只是没有按照你想要的方式进行。
Spark
不是数据库。那么你所说的索引
是什么意思?你可以在Spark中编写一个进程来索引原始文本文件。您可以将原始文本文件保存为Parquet
文件,并按需要对数据进行分区。您还希望它为您做些什么? - David Griffin