CockroachDB之本地以及分布式查询处理

原文:Local and distributed query processing in CockroachDB
作者:Raphael Kena Poss
翻译:Vincent

译者注:本文详细介绍了CockroachDB的两种查询处理方式:本地及分布式,其中详细描述了设计分布式引擎的目的,为了达到分布式,还存在哪些遗留问题。以下为译文。

CockroachDB之本地以及分布式查询处理_第1张图片

当CockroachDB节点接收到查询SQL时,大概会发生什么事情呢:

CockroachDB之本地以及分布式查询处理_第2张图片

pgwire模块负责与客户端应用通信,从客户端接收查询请求。将SQL文本分析并转换为抽象语法树(Abstract Syntax Tree,简称AST)。然后进一步分析并将其转换为逻辑查询计划,该计划是关系运算符的树,如过滤器,渲染(项目),连接。 顺便说一下,逻辑计划树是由EXPLAIN语句报告的数据。

然后将逻辑计划交给负责执行查询的back-end层,并生成要返回给客户端的结果数据。

CockroachDB里有两种back-end:本地执行引擎和分布式执行引擎。

本地查询处理

本地执行引擎能够直接在客户端应用程序连接的节点上执行SQL语句。它主要在本地处理查询,在一个节点上:它需要的任何数据都可以在集群中的其他节点上读取,然后复制到处理节点上以构建查询结果。

CockroachDB的本地引擎的架构大致遵循Goetz Graefe于1993年描述的火山模型(PDF链接)。从软件架构师的角度来看,逻辑查询计划中的每个节点都像一个有状态的迭代器(例如Python发生器做什么):在树的根上迭代生成所有结果行,并且在计划树的每个节点处,一次迭代将在树中进一步消耗零个或多个节点的迭代。树的树叶是表或索引读取器节点,它向CockroachDB的分布式存储层发出KV查找操作。


CockroachDB之本地以及分布式查询处理_第3张图片

*逻辑计划实例:
SELECT cust_id, address FROM customer WHERE name LIKE ‘Comp%’ AND state = ‘CA’
假设cust_id就是主键,并且customer(name)上面建立了索引。*

从代码的角度来看,每个关系操作符都是作为迭代器的“下一个”方法的实现;当执行查询时,迭代器的树是有序的进行处理:每个节点的“下一个”方法都处于等待状态,直到源节点完成自己的“下一个”方法调用。从外部看,查询执行逻辑逐行处理数据并作出决定(例如,保留/删除行,计算派生结果)。处理本质上是连续的。

简单是该引擎的主要特点。

该引擎的代码可以仅使用本地推理进行审查和验证,从而保证正确性; 对此我们(CockroachDB开发人员)是比较信任的。

另外,因为处理是在本地执行的,所以说如果本地(在相同的节点上)有需要的所有数据,并且/或者在源表/索引中只有少数行进行处理时,则可以非常快速地提供结果。

本地并行处理UPDATE

客户端应用程序中的常见模式是在单个会话/事务的一行中发出多个INSERT或UPDATE(或UPSERT或DELETE)语句。同时,CockroachDB中的数据更新必须比大多数其他SQL引擎持续更长时间,因为为了保持一致是需要强制性的网络流量。

我们找到了自己的困惑:我们为什么不能通过并行地执行它们从而加快数据写入的处理呢?这样的话,虽然修改单个数据的语句的延迟更高,但如果有多个的话,那么是可以降低总体的延迟。

然而,这种调整带来的改变是微不足道的。

在客户机应用程序和数据库服务器之间被看作是API时,标准SQL语言有一个不方便的属性:它不允许并行处理多个查询。

SQL语言的设计者已经规定每条SQL语句执行时的状态应该“就假设之前的语句已经执行完成一样”,特别是PostgreSQL和CockroachDB采用的方言。例如,如果在INSERT之后紧跟着执行了一遍SELECT,那么SELECT时就必须假设已经INSERT执行完了。除此之外,SQL“API”或“协议”是会话式的:每个语句都可能有一个结果,而客户端应用程序在决定下一步将运行哪个语句之前,是可以观察结果的。例如UPDATE完成之后也会有一个返回结果:影响了多少条数据。客户端可以使用UPDATE去更新一条数据,如果UPDATE的结果显示影响0条数据(不存在这样的数据),那么会决定发出INSERT。

SQL语言的语义特征是非常有用的;它们赋予了客户端应用很多的控制。当时为了把这些特征给包含到SQL中而做出的选择,无意当中却导致了SQL并行自动化执行成为了不可能。

自动化并行是什么样子的?在计算机科学当中这是一个非常经典的问题。当处在足够高的级别上时,每个解决方案看起来都是一样的:从应用程序接收指令/操作/查询的处理引擎必须找出哪些操作在功能上独立于它们之前和之后。如果客户端应用或者程序向处理引擎发送指令“处理A,再处理B”,那么处理引擎会确认B是不需要A提供任何结果的,如果B在A之前完成,则A不会受到影响,可以在A完成之前启动B(可能在同一时间点),以便A和B并行执行。当然,每一个返回给应用或者程序的操作结果必须看起来好像是严格按照顺序执行的。

使用标准SQL,一旦数据修改语句与同一个表上的SELECT进行交错,就很难确定。

然而,当我们与某些对写延迟有特别兴趣的用户讨论这个问题时,我们达成了一致的观点:我们可以扩展我们的SQL方言,以提供可以并行处理的CockroachDB特定的语法扩展。这是可以被接受的的,因为与延迟更高的交易导致的持续业务成本相比,更新客户端代码以添加必要的注释的一次性前期成本是可接受的。

可以在我们的库中找到详细的设计。为了利用这个新功能,客户端应用程序可以使用特殊的子句RETURNNING NOTHING来执行INSERT/DELETE/UPSERT/UPDATE。当使用RETURNING NOTHING发出两个或多个数据修改语句时,本地执行引擎将同时启动它们,它们可以并行地进行。只有当事务被提交时,引擎才会等待直到完成所有的数据更新。

CockroachDB的分布式查询处理

除了本地引擎,CockroachDB还提供了一个分布式执行引擎。这样做的作用是将单个SQL语句所需的处理部分委托给集群中的多个节点,这样处理就可以并行地在多个节点上进行,并有望完成得更快。我们还可以期望它消耗较少的网络流量,例如可以在源代码中应用过滤器。

这篇文章详细介绍了原因和大概的方式。我们将分两篇单独的文章,进一步说明它是如何工作的。

颠覆旧观念

分布式处理引擎常见的原因是因为我们通过观察发现查询的数据通常分布在集群中的多个节点上。相比将数据放在单个处理节点上,直觉告诉我们可以将把对数据的计算进行转移,这样做可以节省处理时间(=使查询更快)。

然而,当你的思路处在更高层级,你会发现这种想法还是太low了:如果仅仅是出于这个目的,那么除了查询分发之外,还有许多可能的解决方案。

例如,有人可能会说,已经有很好的非分布式解决方案来提高性能。

可以将过去30年来那些伟大智慧的结晶归结为:通过历史观察可以这样说,生产代码中的数据工作负载要么是由小型的突发事务组成,这些事务需要的数据视图得是最新的,并且还得是一致的,但是只涉及到非常少的数据量(OLTP工作负载),要么就是由那些长久的、跨度大的并且只读的事务组成,这些事务涉及到数据量就比较大了,但是通常对数据视图不要求是最新的,也不要求是一致的(分析工作负载,线上或者是线下)。

从这个观察可以看出,OLTP的工作负载主要是在分布式存储系统中与少量的节点进行通信(因为主要和辅助索引会将工作缩小到存储中的几行),而分析工作负载可以在物化视图上面运行,这些视图是在在单独的系统中异步维护的,以一致性为代价(因为它们不需要更新任何内容)对增大吐吞量进行优化。无论在哪种情况下,分布式处理都不能体现出明显的增值。

分布式处理的另外一个常见的动机是为了向前面提到的30年的智慧结晶发起挑战,承认在互联网服务中新型工作负载的冉冉升起:现在最常见的是OLTP工作负载在更新少量数据之前,需要读取大量的数据,而分析工作负载的好处就是在预知需要读取少量最新的数据即可。在这两种情况下,分布式处理看似都提供了有效的解决方案,可以使它们变得更快。

然而,与更高级的再相比,它们还是显得比较弱,因为由于这些工作量已经变得司空见惯,我们已经看到了一些看起来更简单、更有效的技术和标准的出现,正是为了解决这些用例。

对于在执行UPDATE之前,需要做事务性的相关工作,包括大量的数据读取,那么常用的方法是使用合适的缓存。Memcached以及其他相关技术的出现就是为了解决这个问题的。对于需要最新数据的分析处理,额外的复制堆栈可以确保分析输入既是最新的也可以快速访问,这些堆栈是用来维护物化视图的一致性。良好的缓存和事务/事件日志记录是实现这一目标的有名的也是有效的技术手段,并且相对于通用的分布式处理引擎,供应商更容易提供相关技术。

在新的计算需求的表达与加速它们的专门解决方案的设计之间来来往往是计算机和软件架构师的主要工作,并且这也是计算历史上最经常出现的绘图设备。毕竟,它“只是工作”,对吧?

虽然运行了,但它是如此的复杂!

在这里,“复杂”是使用需要付出昂贵代价的委婉说法。

分布式查询处理的初衷

在这个故事中,我们揭示了计算机历史上第二种最常见的绘图设备:排除复杂性。它的工作原理介绍如下:

“作为程序员,我真的不想了解所有这些专门的东西。我只想开发我的应用程序!”

“作为公司老板,我不想为了达到业绩,而需要与10家不同的技术提供商打交道。我的瑞士军刀在哪?”

如果没有通用的分布式计算引擎,开发人员或技术人员处理数据必须时刻牢记许多问题的答案:我的应用程序在数据库中抛出什么样的工作量?我需要什么二级索引来使查询更快?我需要采用哪个第三方缓存技术?使用哪个栈来跟踪数据仓库中的更新事件?应该使用哪些客户端库来保持一致的数据视图,以避免服务器上花费长时间的延迟查询?

设计一个互联网规模的应用程序或服务所需要的知识,是需要花费很多时间的,相应的操作成本(软件+人力资源)也超出了一定的范围。

这就是我们之所以只要在CockroachDB设计分布式处理的原因:一种多功能工具,可以在数据附近执行任意计算

我们的长期目标是帮用户消除必须考虑任意复杂操作的原子性,一致性,隔离性和耐久性的负担,也不要考虑来自不同供应商的工具之间的集成。

简而言之,我们的目标是提高正确性,操作简单性和更高的开发人员可伸缩性能的效率,尤其是在普通的情况下,作为一种良好的补充。

这是建立CockroachDB以后一种自然而然的延伸。毕竟,CockroachDB在新一季的NoSQL热潮之后,再次引起了对NewSQL的兴趣:软件社区已经尝试管理客户端的交易和模式,但是失败了,并已经承认在数据库引擎层面进行处理,就正确性,简单性和生产力方面来说,既有直接优势,也有间接的优势。

我们的分布式处理引擎扩展了这一原则,并在此基础上提出了一些更复杂的计算需求。

这包括当维护二级索引和物化视图的良好组合是不切实际或太不利于更新性能时,需要开始启动支持执行SQL查询。

它还包括支持一些经常执行的大量聚合的分析工作,其中结果需要最新的输入数据。

但是,最终我们也希望能够满足更大的工作量。 例如,我们非常尊重制作Apache Samza的愿景,我们鼓励您观看Martin Kleppmann的演示文稿“将数据库外出”,以了解我们的目标的总体方向。

“内置电池!”

因此,我们将在CockroachDB中实施分布式处理引擎。

它受到Sawzall的启发,分布式处理的高级之处如下:

  • 来自客户端应用的请求被转换为分布式处理计划,类似于数据流处理网络的蓝图。

  • 接收到查询的节点然后将该查询计划部署到集群中的一个或多个其他节点上。此部署包括在远程每个节点上创建“虚拟处理器”(如少量计算引擎),以及它们之间的数据流(如少量专用网络连接)。

  • 启动分布式处理器网络以开始计算。

  • 同时,处理查询的节点从分布式网络收集结果并将其转发给客户端。当所有处理器停止时,处理被视为完成。

我们再次强调,这是一种通用方法:数据流处理网络是理论计算机科学的一个强大的模型,现在被公认为可以处理几乎任何类型的计算。最终,我们希望用户能够以任意方式利用这个通用工具。然而,在初始阶段,我们将其开发限制在一些常见的SQL模式,以便我们可以专注于稳健性和稳定性。

例如,在CockroachDB 1.0中,分布式引擎可以自动处理SQL排序、过滤、简单聚合和一些连接。在CockroachDB v1.1中,它将自动接管更多的SQL聚合和连接。我们将评估社区对第一个方法的反应,以决定在哪里进一步扩展功能。


CockroachDB的未来计划

大量的遗留工作

说实话,在推荐分布式处理作为通用工具之前,我们还需要学习相关知识,从而去回答一些复杂的理论问题。我们现在遇到的部分问题如下:

  • 虽然数据提取处理器可以直观地在数据生命的节点上启动,但其他处理器(如排序或聚合数据的处理器)可以放置在任何位置。应该启动多少?哪个集群节点?

  • 当CockroachDB在节点间自动重新平衡数据时,如何确保计算量保持接近数据? 虚拟处理器应该与他们正在开展的数据范围一起迁移吗?

  • 当分布式查询正在进行时,节点何时出现故障? 处理是否在其他地方恢复,并尝试恢复? 部分数据丢失在某些查询中可以接受吗?

  • 分布式处理如何影响集群的性能? 当一个节点代表另一个节点运行虚拟处理器时,它仍然可以向自己的客户端提供多少吞吐量?

  • 如何确保大型查询不会在许多节点上耗尽网络或内存资源? 如果客户端在分布式计算中关闭其连接,该怎么办?

我们承诺在随后的博客文章中,将与您分享我们在这些方面的进展。

总结:CockroachDB中SQL的处理

现在你知道CockroachDB对于SQL查询支持两种模式:本地以及分布式。

在本地执行引擎中,数据从它本来应该在的位置被拉到了负责处理的某个节点处。XXXXXXXX。

在分布式执行引擎中,数据是被运送到靠近数据存储的地方进行处理的,通常在多个节点上同时运行。在底层,我们正在构建一个通用的分布式计算引擎,使用dataflow网络作为基本的抽象。我们计划稍后向所有用户公开此功能,以满足各种分布式处理工作负载,但目前我们只是使用它来加速一些使用过滤,连接,排序和聚合的SQL查询,特别是那些无法通过,或者是不想使用经典技术(例如索引或异步实体化视图)进行手动优化。

两个引擎都可以同时处于活动状态。然而,由于我们正在致力于分布式执行,所以我们希望用户做一下这种尝试:对于那些可以采用分布式的查询,我们决定默认使用分布式执行。您可以使用SET覆盖此默认值,也可以使用EXPLAIN(DISTSQL)来检查给定的查询是否可以分发。随后的博客文章将进一步详细说明这一点。

你可能感兴趣的:(CockroachDB之本地以及分布式查询处理)