跳至主要內容

Klustron(原KunlunBase) 连接快照共享介绍

Klustron大约 5 分钟

Klustron(原KunlunBase) 连接快照共享介绍

背景

计算节点在执行多表连接等查询过程中,会同时访问多个表,当其中两个表都分布在同一个存储节点上时,就可能会出现连接争抢的问题。

而连接争抢的起因是计算节点为了满足数据一致性的要求,限制同一个用户会话下计算节点与每个存储节点有且仅有一个 MySQL 连接,从而确保访问两个表时在存储节点上使用的是同一个事务的快照;而 MySQL 连接无法同时处理多个 sql 请求,必须完全处理完当前 sql 之后,才能处理下一个 sql,因此一个会话下需要同时访问同一个存储节点时,就出现了连接争抢。

常用解决方案

为了处理连接争抢问题,计算节点需要将当前正在连接上执行的 sql 的结果物化当本地的临时文件中,腾出该连接给当前最为紧迫的算子使用,使得整个执行流程继续进行下去。

上图便是一个连接争用的例子,用户执行了“select * from t1, t2 where t1.a=t2.a”,并且使用 MergeJoin 算法(假设 MergeJoin 的执行计划代价最优)。

执行时,其中一个 RemoteScan(t1) 算子先执行,从存储节点 A 读取 t1 的数据,并将收到的数据返回给上层的 MergeJoin 算子;接着,MergeJoin 算子从 RemoteScan(t2) 读取数据与 t1 进行连接,从而触发 RemoteScan(t2) 也向存储节点 A 索要 t2 的数据。

但与存储节点 A 的连接还被 RemoteScan(t1) 算子占用着,为了能够腾出一条空闲连接供 RemoteScan(2) 使用,计算节点为 RemoteScan(t1) 算子创建一个临时文件“sql1_tmp.dat”,将尚在执行中的 sql1 的结果临时存放到该临时文件中,后续 RemoteScan(t1) 要读取 t1 的下一行数据时,直接读取临时文件中的内容即可。

当 sql1 彻底执行完之后,与存储节点 A 的连接交给 RemoteScan(t2) 使用。

很显然,如果物化的数据量很大,这一设计虽然满足了事务一致性的要求,但也会带来一系列显著的问题,例如写临时文件的 IO,磁盘空间的占用,以及编码数据产生的 CPU 消耗等等。

因此我们急需一个更为轻量化的解决方案。

更为轻量的解决方案

此前的设计是基于存储节点(即 MySQL )的特性来实现的,这完全是一个向 MySQL 妥协的设计。

设想一下,如果 MySQL 提供一种接口让多个连接共享一个快照的话,计算节点就不用为连接争抢而烦恼了,只需要向每个访问存储节点的算子提供独立的连接就可以了。然后可惜的是,MySQL 并没有提供这样的接口,毕竟它只是个单机数据库。

幸运的是,我们有资深的 MySQL 内核开发大牛,完全能够自给自足。

在对 MySQL 进行改造之后,提供了如下接口:

start
transaction read only from session $(other session thread id);

这个接口用来开启一个只读事务,并且创建一个和指定的其他连接相同的快照,而且这个操作是非常轻量的。

利用这个接口,计算节点就不用因为连接争抢而被迫对 sql 结果进行物化了。

还是前面的那个例子,RemoteScan(t1) 先执行,并占用了与存储节点 A 的连接 RemoteScan(t2) 执行时,由于“连接1”已经被占用,于是建立一条新的到存储节点 A 的连接,并通过调用新提供的接口,从“连接1”处获得了一个快照拷贝,这样以来它就拥有了和“连接1”完全相同的事务快照了。对比以前的设计真的是轻量了很多。

结果

这个接口除了解决连接争用问题之外,还解锁了计算节点多进程并行执行的功能,而这个强大的功能在此之前由于事务一致性的顾虑一直被禁用着。

abc=# explain select count(1) From t;
QUERY PLAN
------------------------------------------------------------------------------------------
Finalize Aggregate  (cost=7961.55..7961.56 rows=1 width=8)
->  Gather (cost=7961.14..7961.54 rows=4 width=8)
 Workers Planned: 2
 ->  Parallel RemotePlan  (cost=7951.14..7951.14 rows=1 width=8)
 Shard :1 Remote SQL:
SELECT count(1) FROM `t` WHERE ($PARTIAL-QUAL)

当扫描的表数据量很大时,优化器会根据统计信息将表拆分为多个不重叠的区间,产生单表上的并行扫描算子;然后基于单表上的并行扫描算子进而产生上层并行的聚合算子。

最终的执行计划如上图所示,计算节点的会话进程 fork 出多个工作进程,各个工作进程并行地扫描着表的不同区间,从而加速sql的执行。在会话进程 fork 工作进程之前,会先确保与存储节点 A 建立了一个连接(假设为“连接1”),并在该连接上开启了一个事务;在 fork 工作进程时,将该连接的标志也传递给了各个工作进程,工作进程在建立与存储节点A的新连接时,就能从“连接1”获得同一个快照了。

总结

本文介绍了昆仑数据库(Klustron)如何通过连接间共享快照的机制,消除了计算节点在连接争用时所进行的数据物化操作,以及在执行并行计划时,如何确保读取的数据是满足事务一致性要求的。

END