Postgresql 在Postgres 13.1中,对具有少量行的表并行化非常昂贵的操作

Postgresql 在Postgres 13.1中,对具有少量行的表并行化非常昂贵的操作,postgresql,Postgresql,我有一个包含少量行的表,其中一个非常昂贵的函数需要在单独的工作进程中的每一行上运行,因为该函数非常占用CPU。通过将存储参数parallel_workers设置为max_worker_processes,我可以强制对表进行并行顺序扫描。下面我创建了一个易于复制的示例,唯一显著的区别是value列的大小实际上是多MB 创建或替换非常昂贵的函数\u operationvalue anyelement,sleep\u time integer=2将整数返回为$$ 开始 执行pg_睡眠时间; 返回睡眠时

我有一个包含少量行的表,其中一个非常昂贵的函数需要在单独的工作进程中的每一行上运行,因为该函数非常占用CPU。通过将存储参数parallel_workers设置为max_worker_processes,我可以强制对表进行并行顺序扫描。下面我创建了一个易于复制的示例,唯一显著的区别是value列的大小实际上是多MB

创建或替换非常昂贵的函数\u operationvalue anyelement,sleep\u time integer=2将整数返回为$$ 开始 执行pg_睡眠时间; 返回睡眠时间; 终止 $$LANGUAGE plpgsql不可变严格并行安全成本10000; 创建未标记的表行 id串行主键, 值uuid 与平行_工人=8; 将generate_series1,16中的gen_random_uuid标识符插入到昂贵的行值中; 解释分析冗长 选择 非常昂贵的操作价值,2 从…起 昂贵的 ; 正如您从解释分析输出中看到的,我确实得到了一个并行计划,但是非常昂贵的函数仍然无法解释地按顺序执行。如果正确地并行化,这个查询应该需要4秒,但是需要32秒,尽管生成了7个额外的工作线程


如何强制postgres在不使用dblink的情况下为每行/昂贵的函数调用分配1个CPU内核?

这里的问题是,所有16个表行都在一个表块中,并行顺序扫描将块的范围分配给每个工作者进行扫描

因此,它们中只有一个扫描单个块并执行所有16个函数调用

在具有更多行的实际示例中,工作负载将分布得更均匀。如果工作台很小,可以通过将fillfactor设置为10并在工作台上运行真空满,人为地使工作台膨胀。对于像这样小的表来说,这并没有多大作用,但可以改进较大表的并行化


另一个愚蠢的想法是对表进行分区,以便跨分区分割行。

这里的问题是,所有16个表行都在一个表块中,并行顺序扫描将块的范围分配给每个工作程序进行扫描

因此,它们中只有一个扫描单个块并执行所有16个函数调用

在具有更多行的实际示例中,工作负载将分布得更均匀。如果工作台很小,可以通过将fillfactor设置为10并在工作台上运行真空满,人为地使工作台膨胀。对于像这样小的表来说,这并没有多大作用,但可以改进较大表的并行化


另一个愚蠢的想法是划分表,这样就可以在分区之间分割行。

它不是一次工作一行,而是分块工作。@THX1138然后我建议使用该函数的值生成一个生成的列,并创建一个索引。它不是一次工作一行,而是分块工作。@THX1138然后我建议使用该函数的值,并对实际示例中的行进行索引,行为~20Mb TOASTed postgis geoms。由于我正在运行的union/polygonize操作的性质,它增加了开销/引入了其他问题以将Geom分割成更小的块。我最初认为问题可能是由于TOAST降低了磁盘元组大小,因此我将TOAST_tuple_目标设置为8160 max,然后创建了一个虚拟文本列,其中填充的字符最多比块限制低一点,我认为这会导致将行均匀地分配给工作人员,但这没有任何影响。@THX1138我认为toast\u tuple\u target不会做您认为它会做的事情。你可能仍然只有一页来放整张桌子。它在烤了你的假柱之后停止了,而不是之前。通过将一个较大但低于toast限制的虚拟列与一个较小的fillfactor相结合,我获得了所需的行为。@LaurenzAlbe在将fillfactor更改为10并在toast限制下添加一个额外的虚拟varchar列后,这确实起作用-通过将大几何体列的存储设置为EXTERNAL,解决了我的实际问题。我确实需要将行数增加三倍,但仍然没有像我发布的简单示例中那样平均分配,但性能提高了3倍。谢谢你的帮助!在实际示例中,行是~20Mb的烤postgis geoms。由于我正在运行的union/polygonize操作的性质,它增加了开销/引入了其他问题以将Geom分割成更小的块。我最初认为问题可能是由于TOAST降低了磁盘元组大小,因此我将TOAST_tuple_目标设置为8160 max,然后创建了一个虚拟文本列,其中填充的字符最多比块限制少一点,我认为这会导致行均匀地分布到工作区,但是
没有任何影响。@THX1138我认为toast\u tuple\u target不会做您认为它会做的事情。你可能仍然只有一页来放整张桌子。它在烤了你的假柱之后停止了,而不是之前。通过将一个较大但低于toast限制的虚拟列与一个较小的fillfactor相结合,我获得了所需的行为。@LaurenzAlbe在将fillfactor更改为10并在toast限制下添加一个额外的虚拟varchar列后,这确实起作用-通过将大几何体列的存储设置为EXTERNAL,解决了我的实际问题。我确实需要将行数增加三倍,但仍然没有像我发布的简单示例中那样平均分配,但性能提高了3倍。谢谢你的帮助!
Gather  (cost=0.00..5312.12 rows=1700 width=4) (actual time=2010.650..32042.558 rows=16 loops=1)
"  Output: (very_expensive_operation(value, 2))"
  Workers Planned: 8
  Workers Launched: 7
  ->  Parallel Seq Scan on public.expensive_rows  (cost=0.00..5312.12 rows=212 width=4) (actual time=286.078..4575.903 rows=2 loops=7)
"        Output: very_expensive_operation(value, 2)"
        Worker 0:  actual time=0.001..0.001 rows=0 loops=1
        Worker 1:  actual time=0.001..0.001 rows=0 loops=1
        Worker 2:  actual time=0.001..0.001 rows=0 loops=1
        Worker 3:  actual time=2002.537..32031.311 rows=16 loops=1
        Worker 4:  actual time=0.001..0.001 rows=0 loops=1
        Worker 5:  actual time=0.002..0.002 rows=0 loops=1
        Worker 6:  actual time=0.002..0.002 rows=0 loops=1
Planning Time: 0.086 ms
Execution Time: 32042.609 ms