追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (2)

查询与存储引擎

查询引擎和存储引擎不同(但RDBMS不存在这种区别,因为存储引擎是专有的,并由查询引擎的同一供应商提供。MySQL是个例外,它能连接到各种存储引擎)。

假设使用查询引擎时,SQL是主要的API(也有其他API支持其他数据模型。您可以将其中某些API映射到SQL,您也可以扩展SQL以支持无法容易映射的API),在该假设前提下,查询引擎必须做到以下几点:

  • 允许客户端与它连接,以便它为客户端提交的SQL查询服务。
  • 在集群中分发连接,以减少排队时间和平衡负载,甚至可以将数据访问本地化。
  • 编译查询。包括对解析查询、标准化查询、绑定查询和优化查询,以及生成由执行引擎运行的最佳计划。基于引擎支持的SQL的广度和深度, 这方面可以非常复杂。
  • 执行查询。这是运行查询计划的执行引擎。它也是与存储引擎交互访问数据的组件。
  • 将查询结果返回给客户端。

同时,存储引擎必须至少具备以下能力:

  • 存储结构,例如HBase、文本文件、序列文件、ORC文件、Parquet、Avro和用于支持键-值的JSON、Bigtable、文件、文本搜索和关系数据模型
  • 横向扩展的分区
  • 自动数据重新分区以实现负载平衡
  • 投影,选择一组列
  • 选择,根据谓词选择一组行
  • 写入和读取的数据缓存
  • 通过键进行聚集,按键值访问
  • 快速访问路径或过滤机制
  • 事务支持/预写或审计日志记录
  • 复制
  • 压缩和加密

它还可以具备以下能力:

  • 混合负载支持
  • 批量数据摄取/提取
  • 索引
  • 数据本地化访问
  • 数据管控
  • 安全
  • 灾难恢复
  • 备份、归档和恢复功能
  • 多温数据支持

部分功能可以在存储引擎中,可以在查询引擎中,也可以共享在这两个引擎中。例如,查询和存储引擎需要协作,以提供高并发性和一致性。

以上列表仅列出部分信息,它们仅说明了查询和存储引擎之间协作的复杂性。

现在,我们已定义了不同类型的工作负载、不同角色的查询引擎和存储引擎。在本报告中,我们将深度分析构建一个支持所有工作负载和众多数据模型的系统所面临的挑战。

挑战:适于所有工作负载的单一查询引擎

查询引擎很难支持单一操作、BI或分析工作负载(事实证明,不同的专有平台会相互支持)。但是,对于一个服务于所有工作负载的查询引擎来说,它必须满足比过去更多的需求。所以,我们正在接触一个充满障碍的新领域,让我们探讨其中的一些挑战。

数据结构-键支持、聚集、分区

要处理这些不同类型的工作负载,查询引擎必须首先确定它正在处理哪种类型的工作负载。假设它是单行访问,如果结构中没有用来减少扫描的键入访问或机制,单行访问可能意味着对一个庞大表格中的所有行进行扫描。查询引擎需要了解该表的键结构,以评估提供的谓词是否涵盖整个键或只是一部分键。如果谓词包含整个唯一的键,那么查询引擎将会了解这是一个单行访问,支持直接键入访问的存储引擎便能迅速地对它进行检索。

关于分片的要点

人们在谈论“分片”(sharding)时,常常将其等同于“分区”(partitioning)。分片是基于某种逻辑实体(例如,区域和客户ID等等)对跨多个集群的数据进行分离。通常,该分离和其机制由应用程序进行指定。如果要跨分片访问数据,在查询引擎层面之上还需要具有联邦功能。
分区是在集群中利用多个文件对数据进行分布,以平衡分配跨磁盘或节点的大量数据,实现对数据的并行访问以减少查询的总执行时间。每个磁盘可以有多个分区,并通过对表的键列指定散列、范围或两者结合来管理数据的分离。大多数查询和存储引擎支持此功能,对应用程序相对透明。
任何时候都不应该将分片等同于分区。从规模、性能和操作可管理性的角度来看,分区替代分片的成本很高。事实上,您可以认为分片和分区在解决应用程序的扩展性时相互补充。如何使用分片和分区是一种应用程序架构和设计决策。
应用程序需要具备分片功能。对跨服务器或集群的数据进行分片能提高扩展性,某些查询引擎可能会为此提供便利。但是,在处理分区数据时,跨分片扩展并行查询比跨MPP集群使用单个并行查询引擎更局限和低效。
如果每个分片具有大量可以跨越大型集群的数据,那么使用分区并对该分片执行并行查询是一种不错的方法。然而,跨分片对数据进行传递、重新分区和传播以实现数据连接却非常复杂和低效。但如果跨分片连接数据查询是不合理的,或跨分片处理较为罕见,此时建议跨集群分片。本报告着重讨论分区。
查询引擎尝试使用其他查询引擎时会遇到很多相同挑战,例如,Postrgre SQL或Derby SQL。在这种情况下,查询引擎从本质上变成了一个跨分片的数据联合引擎(本报告后面部分将对此进行讨论)。>

统计

当查询引擎尝试生成查询计划、或了解一个工作负载是运营型或分析型时,统计必不可少。在前面讨论的单行访问情况下,如果在查询中使用的谓词仅包含某些键列,那么引擎必须确定谓词是否包含前导键列或主键列。假设在前导键列上指定了等式谓词,那么查询引擎需要了解有多少合格行,它需要访问的数据分布在节点上的情况。基于此分区方案——即数据如何分布在节点和这些节点内的磁盘上,查询引擎需要判定它是否应该生成一个串行计划或并行计划,或者是否能依赖存储引擎做出高效判定,并访问和检索正确的行数。为此,查询引擎需要了解符合条件的行数。

查询引擎想要了解合格的行数以生成有效的查询计划,唯一的方法是提前收集有关数据的统计信息以确定合格数据的基数。如果涉及多个键列,则很可能这些键列的组合基数比它们各自基数的乘积小得多。因此,查询引擎必须对键列有多列统计信息。可以收集各种统计数据,但至少需要了解唯一的记录数、列的最低值和最高值,或第二最低值和第二最高值。

倾斜是另一个需要考虑的因素。当数据分布在大量节点上,可能出现大量数据最终只被少数几个节点处理的情况,这些少数节点击败了其他大多数节点,影响了集群上运行的所有工作负载(假设大多数需要这些节点来运行),而其他节点需要等待这些少数节点执行完查询,在此种情况下便产生了倾斜。如果查询引擎须处理的唯一类型的工作负载是OLTP或运营工作负载,则不需要处理大量数据,因此也无需担心数据倾斜。除了数据分区层,还可通过选择较佳的分区键来控制数据倾斜。但如果查询引擎同时处理BI和分析工作负载,倾斜可能成为一个重要的要素。倾斜还取决于用于执行查询的并行计划的数量。

在数据倾斜存在的情况下,数据库无法完全依赖于传统的、大部分数据库系统所采用的等宽直方图。在等宽直方图中,基于所找到的最低值和最高值以及计算出的唯一记录数,根据数值范围,收集的统计数值被划分为相等区间。然而,如果发生倾斜,就很难了解哪个值有倾斜,因为它会落在某个特定区间里,而在该区间范围中存在许多其他数值。因此,查询引擎必须收集更多信息以了解倾斜或使用等高直方图。

等高直方图在每个区间中有相同的行数。因此,如果存在倾斜值,那么它可能跨越更大数量的区间。当然,确定正确的区间高度和由此产生的区间数、调整并突出显示倾斜值与非倾斜值(所有区间可能有不同尺寸),同时在不丢失倾斜信息的情况下最小化区间数,这很难做到。实际上,计算这些直方图更加困难,并会带来很多操作挑战。通常情况下,抽样能快速收集这些统计数据,因为必须对数据进行分类以将它们放入这些区间中。所以,应制定相应策略以逐步更新这些统计信息和确定更新信息的频率。这些都是来自于它们自身的挑战。

非前导键列或非键列的谓词

当谓词不在前导键列而在某些键列上时,事情将变得非常棘手。如果遇到IN或NOT IN谓词,情况将会变得更加复杂。当前导键列值未知时,多维度访问方法(MDAM,Multidimensional Access Method)可提供高效访问。在这种情况下,需要了解不带谓词的前导键列的多列基数,以判断用这种方法访问数据是否比全表扫描更快。如果有不带谓词的中间键列,则它们的基数也是必不可少的。所以,除了那些可以按键值等有效索引进行的查询,例如,运营型的查询之外,则必须考虑多键列。

非键列中存在谓词。这些键列的基数也是很重要的,因为它为减少查询上层需处理(例如,连接和聚合)的行数大小提供了一个方案可能。

上述所有键入和非键入访问基数都有助于确定数据连接策略和并行度。

如果存储引擎是一个列存储引擎,那么其使用的压缩方式(字典、运行长度等)会影响扫描的性能。在这种情况下,由于想尽快减少更多行数,谓词的评估顺序是很重要的,所以,应首先从可以达到最大简化的列的谓词开始。此处,聚集访问、全表扫描或减少对列值扫描的有效机制至关重要。

索引和物化视图

接下来讨论索引。存储引擎支持哪些类型的索引,或者哪些索引是由存储引擎顶部的查询引擎创建?索引为数据访问提供了更高效的备用访问路径。有一类索引仅为索引扫描而设计,通过涵盖查询中需要返回的所有相关列从而避免访问基表。

现在我们来看物化视图。物化视图对于复杂的工作负载十分有用,可以通过对数据的预连接和聚合来提高访问效率。物化视图的实现非常复杂,系统需要判定给定查询是否可以利用现有的物化视图,这被称为物化视图查询重写。

有些数据库对索引和物化视图使用不同的名称,例如投影(projection),但最终目的都是为了判断有哪些可用的备用访问路径,以实现有效的键入访问或聚合访问,从而避免大型全表扫描。

当然,一旦添加索引,数据库就需要保持它们的同步。否则,总响应时间将随其在更新时必须维护的索引数的增加而增加。数据库系统必须为索引提供事务支持,以保持与基表一致。可能还需考虑其他因素,例如,索引与基表的位置相关性。数据库必须处理唯一约束。一个典型的例子是在BI和分析环境中(以及一些其他场景),数据的批量加载需要一个有效机制来更新索引,并确保它的一致性。

索引更多用于运营工作负载,很少用于BI和分析工作负载。另一方面,物化视图主要用于BI和分析工作负载,它是在基表中对数据的物化连接和/或聚合,类似于提供快速访问的索引。在当今商业环境中,客户对支持运营仪表板的业务需求的增加可能会改变这些传统观点。如果物化视图的维护需要与更新同步,则可能会大大加重更新或批量加载的负担。假设可以使用审计日志或版本控制来更新物化视图,即物化视图采用异步方式维护,则影响不会很严重。一些数据库支持用户定义的物化视图,为用户带来更高的灵活性,并且不会给运营更新造成负担。查询引擎应尽可能自动重写查询,以充分利用物化视图。

存储引擎还使用其他技术(例如,布隆过滤器和哈希表)来加速访问。查询引擎需要了解存储引擎所有可用的备用访问路径以获取数据。查询引擎还需了解如何利用或执行自身的功能,以便为运营和分析工作负载提供高性能。

并行度

到此,我们已了解如何扫描一个特定的表,能大概估计存储引擎完成扫描后返回的行,也明白了数据如何跨分区传输。现在,我们可以同时考虑串行和并行执行策略,并对并行策略可能的更快响应时间和并行的开销进行平衡。

是的,并行也存在开销。您需要在多个节点上分配更多进程,每个进程都占用内存,在其节点中竞争资源,而且该节点可能会出现故障。您还必须为每个进程提供执行计划,所有进程必须做一些设置以完成执行。最后,每个进程必须将结果发送到单个节点,这个节点整理所有数据。

所有这些结果将可能会导致进程之间有更多信息传递,增加数据倾斜的可能性等。

在给定上述开销的情况下,优化器需要通过使用多个潜在的串行和并行计划来计算处理这些行的成本,并评估哪些是最有效的。

如果要为所有工作负载(包括在秒级或亚秒级时间内执行大量并行查询的EDW工作负载)提供非常高的并发性,优化器需要对每个查询所需的并行度进行评估。为了在最短响应时间内最高效地利用资源来执行查询,查询引擎应将需要处理的行的基数作为每个操作并行度的依据。通常情况下,扫描通过筛选行、连接和聚合能大量减少数据。例如,如果用5个节点能达成目标,那么就没有必要用100个节点。不仅如此,通过处理数据的基数,您能得到查询所需的最大并行度,即查询会被分配到集群中的分段,或集群中的节点子集。如果集群被分成多个相等的分段,为了高效地使用集群,可以将查询分配给这些分段或分段集合,以大幅增加并发。这不仅能提高使用系统资源的效率,还能通过减少并行度来获取更高的弹性。如图1-2所示。

追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (2)

图1-2 基于查询所需的并行度的节点。每条垂直线代表一个节点(共计128个节点),每个色带是32个节点组成的分段。恰当分配查询可以增加并发、效率和弹性,同时降低并行度。

随着集群扩展和更新的技术用于新节点,新节点比集群上现有节点可能拥有更大的资源容量,分配更多查询给新分段能更高效地使用容量。

减少搜索空间

迄今讨论的选项为优化器提供了大量潜在的良好查询计划。目前,有各种各样的技术,例如,NonStop SQL(现在是Apache Trafodion的一部分)和Microsoft SQL Server使用的Cascades算法,它是很好的优化器,但缺点是查询计划的搜索空间过大以至于难以计算出结果。对于长时间运行的查询而言,通过扩大搜索空间、并花费更多时间来寻找更优计划可以获得巨大回报。但对于运营查询而言,寻找更优计划的回报减少地非常快,用于寻找更优计划的编译时间成了一道难题,因为大多数操作查询需要在几秒甚至在亚秒内完成。

解决运营查询编译时间问题的一种方法是提供查询计划缓存。查询缓存的命中策略不仅仅是使用简单的字符串匹配方式,更复杂的做法是实现排除查询语句中的常量和变参,但这还是不够的。自上次执行计划生成后,表定义可能发生变化。在这种情况下,需要使缓存计划失效。表的schema可能更改,但在查询文本中没有不同。处理数据倾斜查询计划与处理无数据倾斜查询计划有显著不同。因此,需要复杂查询计划缓存机制来减少编译时间,同时避免陈旧或低效的计划。查询计划缓存需要主动管理,需要从缓存中删除最近最少使用的计划,保留经常使用的计划。

优化器可以是基于成本的优化器,但必须是有规则驱动的。通过添加经验和规则,优化器得到升级,从而能非常高效地和轻松地处理不同工作负载。例如,它能识别模式。星型连接不可能出现在运营查询中,但对于BI查询,它能检测出此类连接。因此,它能使用为该目的设计的专用索引,或者它能决定做维度表的交叉乘积(优化器会避免),再对事实表进行嵌套连接,而不是扫描整个事实表并对维度表执行重复的哈希连接。

连接类型

上面的讨论为我们引出了连接类型的问题。对于运营工作负载而言,数据库需要支持嵌套连接并为嵌套连接建立缓存。对于嵌套连接而言,外表的非排序属性往往会导致对内表的访问会出现重复,在这种情况下,在内存中为内表建立缓存将有助于提高连接性能。

对于BI和分析工作负载而言,合并或混合哈希连接可能更有效。有时嵌套连接对于此类工作负载可能有用,然而,随着将要连接的数据量的增长,嵌套连接性能将会急速下降。

由于错误的选择会对查询性能产生严重影响,因此您需要对成本增加一定的权重,而不是仅仅根据成本选择计划。即尽管存在一个比哈希连接成本稍低的嵌套连接,优化器也不会选择它,因为它更可能是一个错误的执行计划,而且基数评估也只是评估,并不精确。如果选择了嵌套连接或串行计划,而且运行时符合条件的行数等于或低于编译时估计,那么这是一个优质计划。反之,则嵌套连接或串行计划就变得不是仅仅有点儿糟糕,而可能是毁灭性的。所以,预估嵌套连接和非并行计划时应增加成本权重,使哈希连接和并行计划更受青睐,以避免生成糟糕执行计划的风险。由于不同工作负载对成本有不同要求,您可以调整成本权重,尤其是在考虑运营查询和BI或分析查询之间的平衡时。

对于BI和分析查询而言,如果哈希连接或排序处理的数据很大,检测内存压力和恰当溢出到磁盘就很重要。当然,运营型查询通常不需要处理大量数据,因此,对运营型查询而言,不会面临这样的问题。

数据流和访问

查询引擎的架构需要能处理具有BI和分析工作负载的复杂操作的大型并行数据流,以及快速直接访问运营工作负载。

对于可能处理大量数据的BI和分析查询,查询执行架构应该能在多个级别并行。第一级是分区并行性,它使操作的多个进程并行处理,例如,连接和聚集。第二级是运算符级别或运算符并行性,即扫描、多个连接、聚合以及执行查询的其他操作能并发运行。在同一时间内,查询不应仅仅执行一个操作,或许应像MapReduce一样将磁盘上的中间结果进行物化。

所有进程的执行都应该与数据流从扫描到连接、再到其他连接和聚合的操作同时执行。这带来了第三种并行,即管道并行性。在查询计划中允许一个运算符(例如,一个连接)使用由另一个运算符(例如,另一个连接或扫描)生成的行,一组上下进程间消息队列,或进程内的内存队列,用于保持这些运算符之间的数据流的一致(见图1-3)。

运算符级并行度

图1-3描述了优化器如何根据行基数,预估每个运算符在执行某一步骤时所需的并行度。这可以表现为并行度为二的扫描,另一个扫描和并行度为三的GROUP BY,以及并行度为四的连接。正确的并行度能被执行查询的每个运算符使用。这比每个操作都使用整个集群的设计更高效。本文第13页的“并行度”部分也对此进行了讨论,并介绍了确定整个查询并行度所需的信息,见图1-2。>

追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (2)

图1-3 利用不同层次的并行度

但对于OLTP和运营查询,该数据流架构(图1-4)会来带巨大开销。如果您正在访问单独一行或几行,则不需要队列和复杂数据流。在这种情况下,您可以进行优化以减少路径长度,快速访问和返回相关行。

追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (2)

图1-4 数据流架构

当您使用快速路径优化OLTP查询时,对于BI和分析查询而言,您需要考虑预取数据块,前提是存储引擎支持该功能,而查询引擎正忙于处理前一个数据块。因此,查询引擎处理不同类型的工作负载(处理的本质大相径庭),它还须满足不同工作负载的需求。
图1-5至1-8解释了处理不同情景的不同方法:可以是从单行或单个分区访问串行计划、复杂并行访问、或BI和分析查询多层次的并行处理,以帮助完成复杂的聚合和连接。

追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (2)

图1-5 聚集在键列的单行或一组行的读取和写入的串联计划。例如,为客户插入、删除或更新某一单行,或访问某一特定交易日期的所有数据,这些数据都驻留在同一分区。

追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (2)

图1-6 串行或并行计划,基于成本计算,其中主进程跨区直接访问多个分区的行。这种情况发生在主进程将要处理少量几行或者并行聚合/连接不是必需或没有帮助时。例如,需要访问的客户数据基于交易日期分布在多个分区。

追寻终极数据库 - 事务/分析混合处理系统的交付挑战 (2)

图1-8 在并行计划中,大量数据需要处理,多个连接或连接都需要重新分区和广播数据。

混合工作负载

HTAP面临的最大挑战之一是处理混合工作负载,即在同一个集群、节点、磁盘和表中同时运行OLTP查询、BI和分析查询。查询引擎的工作负载管理功能根据数据源、用户、角色等对查询进行分类,允许用户对工作负载区分优先级、并为某些工作负载分配比其他工作负载更多的CPU、内存和I / O资源。或者,可以优先处理较小的OLTP工作负载,再处理BI和分析工作负载。根据不同查询的需求,进行不同的资源配置。

但是,还需要优化存储引擎。存储引擎应自动降低耗时较长查询的优先级,并在执行高优先级查询时,暂停执行其他查询,待高优先级查询处理完后,再返回处理耗时较长的查询,这被称为防止饥饿机制。因为当单个查询要占用所有资源时,您不希望因处理相同优先级或低优先级的查询而不能满足高优先级的查询。解决此问题的另一种方法可能是,在存储引擎能保证一致性的情况下,将特定行的更新操作路由到主分区,而将查询操作路由到复制集群。

数据流

越来越多的应用需要源源不断地处理实时数据流,对这些数据进行转换和汇总,并触发相应的操作,通常根据行数或时间窗口处理时间序列数据。这与处理保存在磁盘或内存的表格数据所使用的统计性或用户自定义函数、复杂运算、聚合、甚至OLAP窗口函数有很大不同。虽然Jennifer Widom在2008年提出了新的SQL语法来处理流式数据,但目前尚没有标准的SQL语法来处理流数据。查询引擎必须能处理这种新的数据处理范型。

功能支持

最后,您需要为运营和分析工作负载提供支持的功能列表。运营工作负载这些的功能包括参照完整性、存储过程、触发器;各种级别的事务隔离和一致性;物化视图;快速/批量提取、转换和加载(ETL)功能;以及OLAP,时间序列、统计、数据挖掘等功能,以及为BI和分析工作负载服务的其他功能。

这两种类型的工作负载有很多共有功能。查询引擎需要支持的一些功能是标量和表映射用户定义函数(TMUDF),内、左、右、全外连接; 子查询优化(例如,将嵌套子查询扁平化、将相关子查询转成连接)、谓词下推、排序回避策略、常量折叠和递归联合等(本文不一一列举)。

为不同工作负载提供所有这些功能,需要巨大的资源投入。

关于作者

Rohit Jain是Esgyn的联合创始人和首席技术官。Esgyn是一家开源数据库公司,致力于构建融合型分布式大数据平台。2015年,惠普将Apache Trafodion(企业级大数据MPP SQL数据库)捐赠给了Apache软件基金会。在Apache Trafodion的基础上,EsygnDB的愿景是建立一个能处理任何数据、任何大小和任何工作负载的融合型分布式大数据平台。在过去的28年中,作为一个资深数据库专家,Rohit在应用程序和数据库开发领域曾为Tandem、Compaq和Hewlett-Packard工作过。他经验丰富,主要涉及在线事务处理、运营数据存储、数据集市、企业数据仓库、BI和大规模分布式并行系统的高级分析。