Citus是PostgreSQL世界观下,少数成功突围的商业化的开源产品(相比较pgxc、xl等官方几乎没有维护来说)。
1 前身 pg_shard
citus实现不能说完美,但的确相当精巧,现在citus项目的代码迭代已经非常多了,想要从当前代码进行一些逻辑分析,是相对比较困难的,但citus的前身,pg_shard项目却有一个简单直观的执行模型,可以相对简单地进行观察,之后,也可以看到citus在此基础上,进行的进一步的优化和改造动作。
1.1 pg_shard主体代码结构
这里分析的主入口,就是插件初始化时候的_PG_init。
1.1.1 插件init初始化阶段
主要牵涉到五个不同的入口
分别是:
执行计划hook:PgShardPlanner
执行启动时候的hook:PgShardExecutorStart
实际执行的hook:PgShardExecutorRun
执行完成的hook:PgShardExecutorFinish
执行结束的hook:PgShardExecutorEnd
这里也提供几个选项(参数均为任何时候可以设置,不需要重启数据库):
AllModificationsCommutative 对应选项pg_shard.all_modifications_commutative,如果设置为true,则所有修改操作都会加共享锁,否则update和delete会加排他锁
UseCitusDBSelectLogic 对应选项 pg_shard.use_citusdb_select_logic 是否直接查询逻辑表而非shard,这里会标记查询类型为PLANNER_TYPE_CITUSD
LogDistributedStatements 对应选项pg_shard.log_distributed_statements 是否记录分布式(对每个shard)的执行语句
1.1.2 查询计划Planner代码结构
这里实际上分了三部分处理,对于shard表,进行分布式计划处理,对于citus元数据,pg原生表则继续使用pg本身的逻辑(也就是说,对于多个sharing入口,元数据需要自己维护一致性)
接下来看pgshard的分布式处理,由于代码过多,这里进进行关键逻辑分析。
BuildDistributedPlan 创建分布式执行计划
DistributedPlan数据结构
执行列表taskList
目标列表targetList
标记是否是多源查询selectFromMultipleShards
对于需要创建临时结果表的,提供临时表建表语句createTemporaryTableStmt
DistributedQueryShardList 获取查询中需要访问的shard的列表
LookupShardIntervalList 从表id查询shard列表,并缓存LoadShardIntervalList的结果
PruneShardList 根据查询条件确定终的shard列表
对于range,append调用 BuildRestrictInfoList
对于hash 调用HashableClauseMutator
对于需要访问多个shard节点的查询,下推行列过滤语句(不包括join),然后设置临时表来缓存数据
RowAndColumnFilterQuery 设置过滤语句
BuildLocalQuery 设置本地查询
PlanSequentialScan 对于逻辑表的查询指向临时表
CreateTemporaryTableLikeStmt 创建临时表
deparse_shard_query 生成对应的查询用SQL语句(不同版本分别处理),get_shard_query_def 实际执行的函数
with子语句
union等组操作语句
get_basic_select_query获取基本的查询语句
处理distinct
拼接列名称
处理from条件
处理where条件
处理group by条件
处理having条件
加锁语句 (for update,share)
order by语句
limit语句
1.1.3 查询执行前PgShardExecutorStart代码结构
这里对于只访问一个shard的情况,PreventTransactionChain关闭掉分布式事务,处理事务相关的锁,因为不需要缓存结果集,因此实际执行到后面的PgShardExecutorRun部分执行
对于访问多个shard的情况,则在这一步就创建临时结果集:
执行create temp table like创建临时表
ExecuteMultipleShardSelect执行多个节点的查询
for循环(这里不是并行的)对每个shard分别执行ExecuteTaskAndStoreResults
UnregisterSnapshot,UpdateActiveSnapshotCommandId,RegisterSnapshot 升级快照让数据可见
RangeVarGetRelid 让实际查询指针指向临时表
1.1.4 查询执行中PgShardExecutorRun代码结构
这里首先拒绝掉(对shard的查询)不支持的查询:
如果scan是forword的情况
如果使用游标而非直接结果的情况
queryDesc->totaltime 更新查询的执行时间
对于insert,update,delete执行ExecuteDistributedModify
for循环逐个执行
GetConnection获取节点连接
PQexec执行节点上查询
PQcmdTuples 处理查询的结果集
返回 affectedTupleCount(对于修改来说,只需要返回影响行数)
对于select,执行 ExecuteSingleShardSelect,其他情况直接报错,需要注意的是,对多个shard的查询之前实际上已经在start部分处理,因此这里仅处理单个shard的查询,执行task来自linitial(tasks列表个ta’s)
执行ExecuteTaskAndStoreResults来获取查询的实际结果集
MakeSingleTupleTableSlot创建结果的描述信息
while true循环来处理收取到的结果集
ExecuteTaskAndStoreResults
for循环执行task的taskPlacementCell
SendQueryInSingleRowMode执行查询
StoreQueryResult保存结果集
1.1.5 查询执行完成PgShardExecutorFinish代码结构
这个的作用主要是清理掉期间的数据结构
标记es_finished=true
1.1.6 查询结束PgShardExecutorEnd代码结构
这个的作用主要是清理掉期间的数据结构
标记es_finished=true
这里两个hook完全相同,应该是为后续定制预留入口。