PG 向量化引擎 向量化引擎是 OLAP 数据库提升性能的有效技术。翻到 PostgreSQL 邮件列表有对向量化引擎的讨论。这里进行整理,以作分析。
作者邮件
代码位于 https://github.com/zhangh43/vectorize_engine ,并且合入了 PG13 中。其基本思想是扩展 TupleTableSlot ,引入 VectorTupleTableSlot (一个由投影列组织的列数组)。每列的数组在内存中连续。这使得表达式计算时能够很好使用缓存,并且可以使用 SIMD 。我们已经重构了 SeqScanNode 和 AggNode ,目前支持 VectorTupleTableSlot 。 下面时我们设计的特点: 1) 纯扩展。不会将任何代码解码到 PG 内核中 2) CustomScan 节点。我们使用 CustomScan 框架来替换原有的执行器节点,如 SeqScan 、 Agg 等。基于 CustomScan ,我们可以扩展 CustomScanState 、 BeginCustomScan() 、 ExecCustomScan() 、 EndCustomScan 接口来实现向量化执行逻辑。 3) Post planner hook 。产生 Plan 后,使用 plan_tree_walker 来遍历执行计划树,检测是否可以向量化。如果可以,那么使用向量化节点(以 CustomScan 节点的形式)替换非向量化节点(如 SeqScan 、 Agg 等)。如果不可以,重新转换到原始执行计划,并使用非向量化执行器。未来会改进这一部分,例如当一些节点不能向量化时不再转换到原始执行计划,而是使用 Batch/UnBatch 节点来产生一个向量化和非向量化节点来兼容。 4) 支持逐步实现一个新的向量化执行节点。当前仅支持向量化 SeqScan 和 Agg ,但是开启向量化插件后,其他包括 Join 的查询也可以执行。 5) 继承原始执行器代码。我们选择了一个更加平滑的方式更改当前 PG 执行器节点并将之向量化,而不是重新写整个执行器。拷贝了当前执行器 node 的 c 文件到我们的扩展中,基于此添加了向量化逻辑。当 PG 改进执行器时,我们可以很方便地将之合入我们插件。我们想了解,通过扩展来实现向量化执行器是否是个好方法? 6) 可拔插存储。 PG 现在已支持可拔插存储了。 TupleTableSlot 被重构抽象为 TupleTableSlotOps 结构。当我们将 PG 升级到最新版本时, VectorTupleTableslot 可以基于此框架完成升级。 我们执行 TPCH ( 10G ) benchmark , Q1 的结果对比: PG 是 50s ,向量化 PG 是 28s 。通过以下方法性能可以得到提升: 1) heap tuple 的解码占用更多 CPU 资源。未来我们会使用 zedstore ,向量化执行器更适合列存。 2) 向量化 agg 并未完全向量化。我们还需要做很多优化。例如,批量计算 hash 值,优化 x 向量化 HashAgg 的 hash 表 3) 将 Datum 转换成真实类型的代价以及反操作的代价都很高,例如 DatumGetFloat4 & Float4GetDatum 。一个优化方法是在 VectorTupleSlot 中直接存储真实类型,而不是 datums 的数组。 相关工作: 1) VOPS 也是一个向量化执行插件: https://github.com/postgrespro/vops. 2) Citus 向量化执行器: https://github.com/citusdata/postgres_vectorization_test 。它使用 ExecutorRun_hook 来运行向量化执行器,使用 cstore fdw 来支持列存储。 注意,现在向量化执行器基于 PG9.6 ,但是可以通过一些努力移植到 master/zedstore 。在朝着这个方向前进时,希望收到反馈,我们不胜感激。
Postgres Professional的Konstantin Knizhnik反馈及作者答复
我认为向量化执行器对 PG 来说是绝对必要的,特别是考虑下到现在我们由列存原型 zedstore 。为了充分利用列存带来的优势,我们绝对需要一个向量化执行器。 但是,我不完全理解 为什么建议将其作为扩展来实现 。是的。自定义节点可以在不影响 PG 内核情况下提供向量化执行。但是为了高效继承 zedstore 和向量化执行器,我们需要扩展 table-AM ( VectorTupleTableSlot 和对应扫描函数)。当然将向量化执行器作为扩展更加容易,但我认为迟早应该将它添加到 PG 内核中。 据我了解,您已经由了一些原型实现(否则您是如何获得性能结果的?)如果是这样,您是否打算发布它,或者您认为应该从头开始开发执行器? 答复 : 原型扩展位于 https://github.com/zhangh43/vectorize_engine 。同意某一天将向量化执行器添加到 PG 内核中。但是这么大的特性,不仅需要改变 table-AM ,还需要改变每个执行器节点,例如 Agg,Join,Sort 节点等。以及表达式计算函数和聚合的 transition 函数、 combine 函数等。我们也需要将之向量化。因此第一步作为一个插件来完成,如果在社区中流行并且稳定下来,我们随时可以合入 PG 内核中。 我们确实希望从社区得到一些关于 CustomScan 的反馈。 CustomScan 只是一个抽象层。通常用于支持用户定义的扫描节点 。但 其他一些 PG 扩展( pgstorm )已经将之用作通用的 CustomNode ,例如 Agg , Join 等。由于向量化引擎需要在所有节点中支持向量化处理,因此遵循上述思路,我们选择使用 CustomScan 。 基于 VOPS 经验的一些担忧: 1) 对于某些类型的查询,向量化模型(列式)性能具有优势,但是对于其他某些类型的查询,他的效率较低。此外,数据以行形式导入数据库。一行一行插入列存非常低效。因此需要某些批量导入工具,可以在导入列存之前缓冲插入的数据。实际上这是数据模型的问题,而不是向量化执行器的问题。但我想在这里表达的是, 最好同时拥有 2 中表示(水平和垂直)并让优化器为特定查询选择最有效的一种 答复 : 是的,一般来说对于 OLTP 查询,行格式更好,而对于 OLAP 查询,列存更好。至于存储类型 (或数据模型),我认为 DBA 应该选择行存储或列存储以用于特定表。至于执行器,让优化器根据成本来进行选择是一个好主意。这是一个长期目标,我们的扩展现在需要返回到原始行执行器来进行 Insert 、 update 、 indexScan 。我们希望我们的扩展可以逐步增强。 2) 列存和向量化执行器对于 select sum(x) from T where... 之类的查询最有效。不幸的是,这种简单的查询在现实生活中很少使用。通常分析查询包含 group by 和 joint 。而且这里的向量模型并不总是最优的(你必须从列中重建行来执行 join 和分组)。为了提高查询执行效率,可能需要为同一数据创建多个不同投影(按属性的不同子集排序)。 这就是为什么 Vertica 支持投影的原因。在 VOPS 中也可以这么做:使用 create_projection 按时,可以执行哪些属性应该是标量,哪些可以向量化。在这种情况下,可以使用标准的 PG 执行器执行分组和 join ,同时执行向量化操作以过滤和持续聚集。 这就是为什么 Q1 在 VOPS 中快 20 倍,而不是原型中的 2 倍。所以我认为列存应该可以维护表的多个投影,优化器应该能够为特定查询自动选择其中一个。投影的同步肯定是一个挑战问题,幸运的是, OLAP 通常不需要最新数据。 答复 : Vertica 中投影很有用,我测试过, VOPS 确实很快。如果你能够将之贡献给 PG 内核,那就太好了。我们的扩展旨在不更改任何 PG 内核代码、用户 SQL 和现有表。我们将继续优化我们的向量化实现:向量化 hashagg 需要实现向量化 hash 表、批量计算 hash key 、批量探测 hash 表等。当然 PG 中的原始 hash 表不是向量化 hash 表。 3) 我想知道向量化的执行器是否应该只支持内置类型和预定义的运算符?或者它应该能够与任何用户定义的类型、运算符、聚合一起使用?当然,支持内置标量类型要容易的多,但这与 PG 的开放性和可扩展性相矛盾。 答复 : 是的,我们应该支持用户定义的类型。这可以通过引入将行类型映射到向量类型的寄存器层来完成。例如 int4->vint4 4) 你有没有想过 VectorTupleTableSlot 中存储数据的格式?它应该是基准数组吗?或者我们需要以更底层格式表示向量(例如对于 rel4 类型的 float 数组) 答复: 我们测试结果显示 dataum 转换不高效,我们准备使用你提到的底层数组格式来实现 datum 数组。
原文
https://postgrespro.com/list/id/CAB0yrem3PYu2qQD4=JOg8y_5QAK+Q+K5yEptxs0t71j5cRyoOQ@mail.gmail.com
