简介
在刚刚结束的2018年中国PG大会上,我做了关于gogudb的报告,会后很多圈内好友因为客观原因不能及时现场交流,留下了很多问题。因此,在会议结束之后,有必要在这里,从实现的角度进行进一步的解释和说明。
架构设计
gogudb期望通过以插件的形式,在若干个PG实例的基础上,构建一个shared-nothing的数据库。因此,gogudb运行在某个PG的实例上,我们称为master server。master server上只是存储了父表以及子表的元数据,父表实际由多个子表组成,各个子表实际是分区表,每个子表对应在其他数据源上的某个表。分区表所在的服务器我们称为remote server,remote server上实际存储了各个远程表,这些远程表实际上是master server的子表。
在操作时,父表和普通表操作方式相同,对用户透明。
技术路线
gogudb主要提供了分区管理功能和外部数据访问功能。
分区管理功能主要是提供了两种分区方式:一种是基于范围的分配,例如按照时间或是ID自增的顺序,将某个表进行分区,可以基于表上的某个字段或是复杂的表达式;另外一种,就是采用一致性hash的方式对表进行分区,分区采用的是某个字段或是某种表达式,和范围分区的区别是需要确定远程数据源上hash值的范围,然后在server_map表中需要指定hash值的范围。
外部数据访问功能,则是提供抽象机制使得本地能够高效、透明的访问远程数据源上的表,能够支持在远程表上进行insert、update、 select、delete操作。
目前,在PG的生态圈中,pg_pathman提供了完善而高效的分区管理功能,并且,pg_pathman中对于分区表的具体实现中,支持了外部数据源上的表,但是,支持不够完整。
另外一方面,PG中提供的postgres_fdw提供了对远程PG上数据源上的表的抽象,可以很好的支持对远程表的透明访问。
最近社区的bruce也在讨论基于fdw来实现PG的sharding,之前德哥也曾经手动使用postgres_fdw和pg_pathman手动搭建了一个sharding的demo,链接在这里:https://yq.aliyun.com/articles/62377
但是,这种实现方式,首先过程是冗长的,大概有上百条SQL,而且随着分表的规模增大而线性增大,另外一方面,在实际的操作,很多简单的操作都没法透明化的发送到远程的子表上,例如create index,drop index等这些表级别的操作;尤其需要指出的是,在实际的sysbench测试中,测试的数据性能比较低。
实现特点
因此,在pg_pathman和postgres_fdw的基础上,我们在功能上和性能上进行了进一步的改进。
在功能上:我们采用在底层实现了远程表的分区,使得用户创建的表的子表是fdw的表,因此,客户两条SQL就可以创建出基于fdw的分区表,其中一条是前面所说的往分区规则表里面插入将要创建的表的分区规则,另外一条则是普通的创建语句,这样就极大的简化了普通的建表操作。另外,我们还将对本地父表的上的日常维护操作,例如: create/drop index, alter table, vacuum, truncayte等操作,都能下推到remote server上来操作子表,这样就极大的简化了日常的维护工作。
在性能上,我们也针对sysbench的测试中发现的问题进行了改进。sysbench测试主要是在一张表上进行kv的查询,体现数据库处理典型互联网业务的能力。
我们在测试中发现,当执行foreign_scan时,在postgres_fdw中执行一条简单的select语句,前后需要发送4条sql,主要包括设置隔离级别(START TRANSACTION ISOLATION LEVEL
),生成游标(CREATE cursor CX for select…),从游标获取结果( Fetch 50 from CX),以及提交事务(COMMIT TRANSACTION),从我们的分析来看,可以使用连接的默认事务隔离以及自动提交,并且使用单行模式获取结果,因此这些都可以改成一条sql,从而大大减少了交互时间,我们在简单的测试环境(DELL R710, 32g 内存的机器),测试结果如下:
- hash分区时,一张表上4个远程分区子表, 1个子表上200000条记录,远程子表和父表共用本地一个PG实例,pgbench 测试TPS 从 20000提升到33000
- range分区时,4个远程分区子表, 250000条记录每张表,父表和远程子表共用本地一个PG实例,pgbench 测试TPS 从20000提升到34000。
但是上面优化是有条件的:一,必须是整个查询计划只访问某个remote server;二,返回的记录数量是比较少的,我们把阈值设置为50,如果太多,其实用原有的游标更合适。
在进一步分析之后,我们发现针对这种简单的sql,如果简化查询优化器和查询引擎,或许能够进一步提升性能。针对这种KV的查询,我们简化了原有的查询优化器和查询引擎,主要是针对KV的查询简化了查询优化器,绕过了原有的过程,直接生成了一个foreign_scan的node,然后在查询引擎中,直接执行foreign_scan的node,不需要走原有的迭代器的模型。在这样优化之后,之前两个测试场景下,hash分区的TPS从33000提升到了50000,range分区的TPS从34000提升到了52000。这个优化的前提就是通过sql中的where条件,可以断定SQL可以在某个远程的子表上完成。这个SQL也可以是个复杂的SQL,例如带有agg或是window function等。
为了实现这个优化,我们增加了新的钩子函数,主要是为查询优化器和执行引擎增加的。我们在自己的钩子函数里面实现了简化之后的查询优化器和执行引擎。为了增加这些钩子函数而不改变pg的代码,我们使用了udis86的反汇编引擎,使用代码注入的方式来增加了新的钩子函数(这是一种打热补丁的常规技术,大家可以google)。
适用场
目前,gogudb可以支持PG96 PG10 PG11版本,不过只支持X86架构下的使用,支持跨节点的join agg sort等等操作,但是,不支持分布式一致性事务。
目前还有很多功能需要进一步的开发。如果大家有任何需求和建议,可以到github上提交issue。