Citus有多个不同的执行程序,每个执行程序的行为都不同,以支持各种用例。对于许多概念而言,分布式SQL似乎必须是一个复杂的概念,但是其原理并不是火箭科学。在这里,我们将看几个有关Citus如何采用标准SQL并将其转换为以分布式形式运行以便可以并行化的示例。结果是您可以看到单节点数据库的查询性能提高了100倍或更多。
我们如何知道某物是分布式的还是单片?
在了解实时执行器的工作方式之前,值得对Citus执行器进行全面的复习。
当Citus收到查询时,我们首先查看它是否具有where子句的分片键(也称为分发列)。如果您要分拆诸如CRM应用程序之类的多租户应用程序,则可能会有一个org_id,您总是会限制查询。在这种情况下,只要org_id是where子句的一部分,我们就知道它的目标是单个分片,因此可以使用路由器执行程序。如果未使用该查询,我们会将查询拆分并跨节点并行发送给所有分片。
作为快速更新,Citus中的一个表是另一个表。如果您有一个表事件并想要分发它,则可以创建32个分片,这意味着我们可以轻松扩展到32个节点。如果您从2个节点开始,则每个节点包含32个分片。这意味着每个节点将一次接收16个查询,并且如果它有16个可用的内核,那么所有工作将并行完成,从而导致2个节点x 16个内核,或者说,与在单个内核上执行相比,速度提高了32倍。
对于后面的示例,我们将仅创建4个分片以简化它们,但是随着添加的分片和对应的内核的增加,事情几乎线性地扩展。
用SQL编写,用MapReduce思考
Citus对实时分析的支持是自从我们早期以来,人们就一直使用Citus的工作负载,这要归功于我们先进的查询并行化。结果就是您能够用标准SQL表示事物,并让Citus的分布式查询计划器完成重写查询的艰苦工作,从而为您提供出色的性能,而无需创建复杂的工程胶带。
深入研究一些示例,从count(*)开始
我们可以开始处理的最简单的查询是count(*)。对于count(*),我们需要从每个分片中获取一个count(*)。首先,针对事件表运行一个解释计划,以了解其运作方式:
QUERY PLAN ------------------------------------------------------------------------------------------------------- Aggregate (cost=0.00..0.00 rows=0 width=0) -> Custom Scan (Citus Real-Time) (cost=0.00..0.00 rows=0 width=0) Task Count: 4 Tasks Shown: One of 4 -> Task Node: host=ec2-18-233-232-9.compute-1.amazonaws.com port=5432 dbname=citus -> Aggregate (cost=11.62..11.63 rows=1 width=8) -> Seq Scan on events_102329 events (cost=0.00..11.30 rows=130 width=0) (8 rows) Time: 160.596 ms
查询中有一些注意事项。首先是它使用的是Citus Real-Time执行程序,这意味着查询正在击中所有碎片。第二个是任务是4个之一。该任务在所有节点上通常是相同的,但是由于它是纯粹的Postgres计划,可以根据数据分布和估算值进行更改。如果要查看所有查询计划,则可以扩展输出以获取所有4个分片的任务。最后,您具有针对该特定分片的查询计划本身。
让我们以集群示例为例:
如果我们要对该集群执行count(*),Citus将重新编写查询并将四个count(*)查询发送到每个分片。然后将所得的计数返回给协调器以执行最终聚合:
性能远远超过count(*)
虽然count(*)很容易看出它是如何工作的,但是您可以执行更多操作。如果要获得四个平均值并将它们平均在一起,则实际上并不会获得结果平均值。相反,对于普通的Citus将执行sum(foo)和count(foo),然后在协调器上将sum(foo)/ count(foo)相除,以得出正确的结果。最好的部分仍然可以编写AVG,Citus负责底层的复杂性。
除了汇总之外,Citus还可以告诉您何时加入并在本地执行这些加入。让我们向事件表中添加另一个表:会话。现在,对于每个事件,我们都将会话ID记录为其中的一部分,以便我们加入。有了这两个表,我们现在想要一个查询,该查询将告诉我们会话的平均事件数,以及上周创建的会话:
SELECT count(events.*), count(distinct session_id) FROM events, sessions WHERE sessions.created_at >= now() - '1 week'::interval AND sessions.id = events.session_id
在这两个表上都分配有会话ID的情况下,Citus会知道这些表在同一位置。使用共置的表,Citus将重新编写查询以将连接向下推送到本地,从而不会通过网络发送太多数据。结果是,我们将从每个分片(而不是所有原始数据)中将2条记录发送回协调器,从而大大缩短了分析查询时间。内部重写的内容可能类似于:
SELECT count(events_01.*), count(distinct session_id) FROM events_01, sessions_01 WHERE sessions_01.created_at >= now() - '1 week'::interval AND sessions_01.id = events_01.session_id SELECT count(events_02.*), count(distinct session_id) FROM events_02, sessions_02 WHERE sessions_02.created_at >= now() - '1 week'::interval AND sessions_02.id = events_02.session_id ...
分布式SQL不一定很困难,但是可以肯定很快
下推连接和并行化的好处是:
- 您不必通过网络发送太多数据,这比在内存中扫描要慢
- 您可以一次利用系统中的所有内核,而不是在单个内核上运行查询
- 您可以超出可以在一台计算机中装载多少内存/内核的限制
希望这次对Citus实时执行器的浏览简化了幕后工作的方式。如果您想更深入地学习,请阅读我们的文档。