如何从0深入PostgreSQL内核写一个执行器算子?

时间:2024-04-28 11:47:46

如何从0深入PostgreSQL内核写一个执行器算子?

大家好,我叫光城,昨天分享了一个主题:如何从0深入PostgreSQL内核写一个执行器算子?今天来总结一下,本篇文章的直播回放可以在b站观看,点击原文或者识别下方二维码即可!

97d22bde560874f5e9fc94004b18e0d6.jpeg

1bf62c73599a14aef97bf52f166a249c.jpeg

b2959f5ab606ba031ad412b5465a86fa.jpeg






1.执行器概论

执行器作为连接查询计划和存储引擎的桥梁,负责从存储引擎读取数据,并基于查询计划树执行对应的算子,并得到最终的查询结果。

执行器的处理模型主要分为两大类:基于拉操作的模型和基于推操作的模型。

1.1 Pull模型

也被称为火山模型,是指从最顶层的输出节点开始,不断从下层节点拉取数据,因此是一种自顶向下的执行方式。

优点

  • 通用性:拉模型不受数据及规模的限制,可以处理任意规模的数据集。

  • 灵活性:拉模型可以灵活控制输出的数量,比如Limit算子及时短路。

不足

  • 阻塞节点:对于排序节点,需要首先读取下层节点所有数据,并根据数据量,选择合适的算法进行内排序或者外排序。

  • 函数调用开销:每条元组在节点之间流动的过程中都会涉及大量的函数调用。

  • 缓存不友好:过多的控制语句、函数调用容易导致缓存失效。

  • 并行不友好。

7c5f3bc65e5d77df48d5df83b7514f5c.jpeg

1.2 Push模型

Push模型:从最底层的节点开始,不断生成数据,并向上层节点进行数据推送,因此是一种自底向上的执行方式。推模型本质是一种基于物化的操作,每一个节点处理所有的输入数据,并将处理后的数据进行物化,并传递给上层节点。

优点

  • 并行友好。

  • 推模型解决了拉模型中函数调用过多和缓存切换过多的问题。由于每个节点内部的处理逻辑相同,缓存使用率也更高。

不足

  • 内存占用大

b68efe68215f7ca673c0e61f6ba4a555.jpeg

1.3 向量化执行引擎

除了拉模型和推模型两大基础模型之外,还引入向量化执行引擎。

  • 每次一个 batch数据而非一行数据,减少函数调用。

  • 配合列式存储 + SIMD指令,提升性能。

2.执行器执行流程

2.1 执行器与上下游的关联关系

1.执行器与算子如何关联?

通过三部曲:ExecutorStart、ExecutorRun、ExecutorEnd。

bb399e92adf56f30455d1f3332c76ec9.jpeg

ea0f1a666d1d1dab75447b39fb47e7b9.jpeg

60a21d12a3d2b9aa07a9c6730a660cf8.jpeg

078f9e1d9312ecfb9e36dfbe8836f3f0.jpeg

afcb7971980429f45e740be0e82d412c.jpeg

c5f8c931a187acb2005d5ea6d83452a5.jpeg

25f8cb3285b6be1e0d34751ca1578cdc.jpeg

2.查询计划与执行器如何关联?

通过Portal。

Portal记录了与执行相关的所有信息,例如查询树、计划树和执行状态。对于用户提交的普通查询语句,执行器会创建一个匿名的Portal对象。游标语句,执行器会创建一个对应的命名Portal对象。

b2bb40420fd7912d10ff592058511eba.jpeg

cf522d6f2bb4aa7325db99de588fd8b8.jpeg

ffed7792141d8e30b7b200e2d957f219.jpeg

3.执行器与存储层如何关联?

通过table am与scan/modifyTable算子进行关联。

2.2 表达式与投影

SQL语句中除了SELECT、FROM、WHERE、GROUP BY等关键字之外的部分,都可以被认为是某种表达式。

例如:a列,a + 1,a * b等等。

表达式名 示例
常量表达式 10
列引用 i, j
位置参数引用 $1
下标 arr[i]
域选择表达式 table.column
运算符表达式 a  > b, x and y, x or y
函数表达式 upper(name),  now()
聚集表达式 coumt(*),  avg(salary)
窗口函数 sum(salary)  over (partition by department)
标量子查询 select (select  max(age) from students) from student;
数组表达式 select  array[1, 2, 3]
Row表达式 select  row(1, 'John Doe')

2.3 表达式实现原理

  • ExprContext

记录下每次表达式评估所需要的tuple,可能来自scan、outer、inner。

TupleTableSlot *ecxt_scantuple;
TupleTableSlot *ecxt_innertuple;
TupleTableSlot *ecxt_outertuple;
  • ExprState

ExprState 是表达式求值的*节点,它包含:

  • 计算表达式的指令(steps)

  • 存储评估的结果slot

  • 存储空值结果

  • 存储scalar表达式评估的结果

  • 实际计算表达式的函数

对于一个表达式树,每个node初始化为ExprEvalOp,ExprEvalStep 存储每一步表达式评估的结果。

3eba9673af4f9fe727241c98ce1ec249.jpeg

c969d8db02f9849962354e06c447af53.jpeg

7dec014823832fdff11f1b36bbc6ad71.jpeg

3.如何写一个执行器算子?

假设有一个数据库需求,需要添加一个数据检查的功能,会检查其输入的数据,并对数据进行验证,如果发现数据不符合条件,则会抛出错误或者警告。

例如:plan如下

-> Assert
  Assert Cond: (i = 1)
  -> Seq Scan

我们如何为数据库新增一个AssertOp算子呢?(不考虑优化器,只考虑执行器)

6c599099ea9162186db50cc2bc1627ec.jpeg

6867950caf9a52561f762dd94d43aa54.jpeg

c7e2b769b0c8269e3ddf259de06de0ef.jpeg

dbeaedf3d75020fa2d001db6d7bbf252.jpeg

f5c9f006b37d29b9d83df8889d88acbe.jpeg

f46b30ab7c8f3d8dca7477a45112b184.jpeg

d0542124fe3e3f6412022543cc58a540.jpeg

7d0b81601c0d540dc05b49ee2e73a364.jpeg

b14350f6ad3b93522c6c13e46d5cceff.jpeg

以上就是本次的分享,欢迎转发与收藏。

674732c040ae3710351a2553e578ea3d.jpeg

1a90fecdbe3a471dabe5d69d8fd7caae.jpeg

a4c958684a16793f1107bb85a451014a.jpeg