Thread: Parallel plans and "union all" subquery
Hi,
I have a question about parallel plans. I also posted it on the general list but perhaps it's a question for hackers. Here is my test case :
select version();
version
----------------------------------------------------------------------------------------------------------------------------------
PostgreSQL 13.1 (Ubuntu 13.1-1.pgdg20.04+1) on x86_64-pc-linux-gnu, compiled by gcc (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0, 64-bit
create unlogged table drop_me as select generate_series(1,7e7) n1;
SELECT 70000000
explain
select count(*)
from (select
n1
from drop_me
) s;
QUERY PLAN
----------------------------------------------------------------------------------------------
Finalize Aggregate (cost=675319.13..675319.14 rows=1 width=8)
-> Gather (cost=675318.92..675319.13 rows=2 width=8)
Workers Planned: 2
-> Partial Aggregate (cost=674318.92..674318.93 rows=1 width=8)
-> Parallel Seq Scan on drop_me (cost=0.00..601402.13 rows=29166713 width=0)
JIT:
Functions: 4
Options: Inlining true, Optimization true, Expressions true, Deforming true
Parallel plan, 1s
explain
select count(*)
from (select
n1
from drop_me
union all
select
n1
from drop_me) ua;
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=1640315.00..1640315.01 rows=1 width=8)
-> Gather (cost=1640314.96..1640314.99 rows=2 width=8)
Workers Planned: 2
-> Partial Aggregate (cost=1640304.96..1640304.97 rows=1 width=8)
-> Parallel Append (cost=0.00..1494471.40 rows=58333426 width=0)
-> Parallel Seq Scan on drop_me (cost=0.00..601402.13 rows=29166713 width=0)
-> Parallel Seq Scan on drop_me drop_me_1 (cost=0.00..601402.13 rows=29166713 width=0)
JIT:
Functions: 6
Options: Inlining true, Optimization true, Expressions true, Deforming true
Parallel plan, 2s2
explain
select count(*)
from (select
n1
from drop_me
union all
values(1)) ua;
QUERY PLAN
--------------------------------------------------------------------------------
Aggregate (cost=2934739.24..2934739.25 rows=1 width=8)
-> Append (cost=0.00..2059737.83 rows=70000113 width=32)
-> Seq Scan on drop_me (cost=0.00..1009736.12 rows=70000112 width=6)
-> Subquery Scan on "*SELECT* 2" (cost=0.00..0.02 rows=1 width=32)
-> Result (cost=0.00..0.01 rows=1 width=4)
JIT:
Functions: 4
Options: Inlining true, Optimization true, Expressions true, Deforming true
No parallel plan, 2s6
I read the documentation but I don't get the reason of the "noparallel" seq scan of drop_me in the last case ?
Best regards,
Phil
On Sun, Nov 22, 2020 at 11:51 PM Phil Florent <philflorent@hotmail.com> wrote: > > > Hi, > > > I have a question about parallel plans. I also posted it on the general list but perhaps it's a question for hackers. Hereis my test case : > > > explain > select count(*) > from (select > n1 > from drop_me > union all > values(1)) ua; > > > QUERY PLAN > -------------------------------------------------------------------------------- > Aggregate (cost=2934739.24..2934739.25 rows=1 width=8) > -> Append (cost=0.00..2059737.83 rows=70000113 width=32) > -> Seq Scan on drop_me (cost=0.00..1009736.12 rows=70000112 width=6) > -> Subquery Scan on "*SELECT* 2" (cost=0.00..0.02 rows=1 width=32) > -> Result (cost=0.00..0.01 rows=1 width=4) > JIT: > Functions: 4 > Options: Inlining true, Optimization true, Expressions true, Deforming true > > > No parallel plan, 2s6 > > > I read the documentation but I don't get the reason of the "noparallel" seq scan of drop_me in the last case ? > Without debugging this, it looks to me that the UNION type resolution isn't working as well as it possibly could in this case, for the generation of a parallel plan. I found that with a minor tweak to your SQL, either for the table creation or query, it will produce a parallel plan. Noting that currently you're creating the drop_me table with a "numeric" column, you can either: (1) Change the table creation FROM: create unlogged table drop_me as select generate_series(1,7e7) n1; TO: create unlogged table drop_me as select generate_series(1,7e7)::int n1; OR (2) Change the query FROM: explain select count(*) from (select n1 from drop_me union all values(1)) ua; TO: explain select count(*) from (select n1 from drop_me union all values(1::numeric)) ua; QUERY PLAN ---------------------------------------------------------------------------------------------------- Finalize Aggregate (cost=821152.71..821152.72 rows=1 width=8) -> Gather (cost=821152.50..821152.71 rows=2 width=8) Workers Planned: 2 -> Partial Aggregate (cost=820152.50..820152.51 rows=1 width=8) -> Parallel Append (cost=0.00..747235.71 rows=29166714 width=0) -> Result (cost=0.00..0.01 rows=1 width=0) -> Parallel Seq Scan on drop_me (cost=0.00..601402.13 rows=29166713 width=0) (7 rows) Regards, Greg Nancarrow Fujitsu Australia
Envoyé : lundi 23 novembre 2020 06:04
À : Phil Florent <philflorent@hotmail.com>
Cc : pgsql-hackers@lists.postgresql.org <pgsql-hackers@lists.postgresql.org>
Objet : Re: Parallel plans and "union all" subquery
>
>
> Hi,
>
>
> I have a question about parallel plans. I also posted it on the general list but perhaps it's a question for hackers. Here is my test case :
>
>
> explain
> select count(*)
> from (select
> n1
> from drop_me
> union all
> values(1)) ua;
>
>
> QUERY PLAN
> --------------------------------------------------------------------------------
> Aggregate (cost=2934739.24..2934739.25 rows=1 width=8)
> -> Append (cost=0.00..2059737.83 rows=70000113 width=32)
> -> Seq Scan on drop_me (cost=0.00..1009736.12 rows=70000112 width=6)
> -> Subquery Scan on "*SELECT* 2" (cost=0.00..0.02 rows=1 width=32)
> -> Result (cost=0.00..0.01 rows=1 width=4)
> JIT:
> Functions: 4
> Options: Inlining true, Optimization true, Expressions true, Deforming true
>
>
> No parallel plan, 2s6
>
>
> I read the documentation but I don't get the reason of the "noparallel" seq scan of drop_me in the last case ?
>
Without debugging this, it looks to me that the UNION type resolution
isn't working as well as it possibly could in this case, for the
generation of a parallel plan. I found that with a minor tweak to your
SQL, either for the table creation or query, it will produce a
parallel plan.
Noting that currently you're creating the drop_me table with a
"numeric" column, you can either:
(1) Change the table creation
FROM:
create unlogged table drop_me as select generate_series(1,7e7) n1;
TO:
create unlogged table drop_me as select generate_series(1,7e7)::int n1;
OR
(2) Change the query
FROM:
explain
select count(*)
from (select
n1
from drop_me
union all
values(1)) ua;
TO:
explain
select count(*)
from (select
n1
from drop_me
union all
values(1::numeric)) ua;
QUERY PLAN
----------------------------------------------------------------------------------------------------
Finalize Aggregate (cost=821152.71..821152.72 rows=1 width=8)
-> Gather (cost=821152.50..821152.71 rows=2 width=8)
Workers Planned: 2
-> Partial Aggregate (cost=820152.50..820152.51 rows=1 width=8)
-> Parallel Append (cost=0.00..747235.71 rows=29166714 width=0)
-> Result (cost=0.00..0.01 rows=1 width=0)
-> Parallel Seq Scan on drop_me
(cost=0.00..601402.13 rows=29166713 width=0)
(7 rows)
Regards,
Greg Nancarrow
Fujitsu Australia
On 23-11-2020 13:17, Phil Florent wrote: > Hi Greg, > > The implicit conversion was the cause of the non parallel plan, thanks > for the explanation and the workarounds. It can cause a huge difference > in terms of performance, I will give the information to our developers. > > Regards, > > Phil > > > > ------------------------------------------------------------------------ > *De :* Greg Nancarrow <gregn4422@gmail.com> > *Envoyé :* lundi 23 novembre 2020 06:04 > *À :* Phil Florent <philflorent@hotmail.com> > *Cc :* pgsql-hackers@lists.postgresql.org > <pgsql-hackers@lists.postgresql.org> > *Objet :* Re: Parallel plans and "union all" subquery > On Sun, Nov 22, 2020 at 11:51 PM Phil Florent <philflorent@hotmail.com> > wrote: >> >> >> Hi, >> >> >> I have a question about parallel plans. I also posted it on the general list but perhaps it's a question for hackers.Here is my test case : >> >> >> explain >> select count(*) >> from (select >> n1 >> from drop_me >> union all >> values(1)) ua; >> >> >> QUERY PLAN >> -------------------------------------------------------------------------------- >> Aggregate (cost=2934739.24..2934739.25 rows=1 width=8) >> -> Append (cost=0.00..2059737.83 rows=70000113 width=32) >> -> Seq Scan on drop_me (cost=0.00..1009736.12 rows=70000112 width=6) >> -> Subquery Scan on "*SELECT* 2" (cost=0.00..0.02 rows=1 width=32) >> -> Result (cost=0.00..0.01 rows=1 width=4) >> JIT: >> Functions: 4 >> Options: Inlining true, Optimization true, Expressions true, Deforming true >> >> >> No parallel plan, 2s6 >> >> >> I read the documentation but I don't get the reason of the "noparallel" seq scan of drop_me in the last case ? >> > > Without debugging this, it looks to me that the UNION type resolution > isn't working as well as it possibly could in this case, for the > generation of a parallel plan. I found that with a minor tweak to your > SQL, either for the table creation or query, it will produce a > parallel plan. > > Noting that currently you're creating the drop_me table with a > "numeric" column, you can either: > > (1) Change the table creation > > FROM: > create unlogged table drop_me as select generate_series(1,7e7) n1; > TO: > create unlogged table drop_me as select generate_series(1,7e7)::int n1; > > > OR > > > (2) Change the query > > FROM: > explain > select count(*) > from (select > n1 > from drop_me > union all > values(1)) ua; > > TO: > > explain > select count(*) > from (select > n1 > from drop_me > union all > values(1::numeric)) ua; > > > QUERY PLAN > ---------------------------------------------------------------------------------------------------- > Finalize Aggregate (cost=821152.71..821152.72 rows=1 width=8) > -> Gather (cost=821152.50..821152.71 rows=2 width=8) > Workers Planned: 2 > -> Partial Aggregate (cost=820152.50..820152.51 rows=1 width=8) > -> Parallel Append (cost=0.00..747235.71 rows=29166714 > width=0) > -> Result (cost=0.00..0.01 rows=1 width=0) > -> Parallel Seq Scan on drop_me > (cost=0.00..601402.13 rows=29166713 width=0) > (7 rows) > > > Regards, > Greg Nancarrow > Fujitsu Australia Hi, For this problem there is a patch I created, which is registered under https://commitfest.postgresql.org/30/2787/ that should fix this without any workarounds. Maybe someone can take a look at it? Regards, Luc Swarm64
On Tue, Nov 24, 2020 at 2:34 AM Luc Vlaming <luc@swarm64.com> wrote: > > Hi, > > For this problem there is a patch I created, which is registered under > https://commitfest.postgresql.org/30/2787/ that should fix this without > any workarounds. Maybe someone can take a look at it? > I tried your patch with the latest PG source code (24/11), but unfortunately a non-parallel plan was still produced in this case. test=# explain select count(*) from (select n1 from drop_me union all values(1)) ua; QUERY PLAN -------------------------------------------------------------------------------- Aggregate (cost=1889383.54..1889383.55 rows=1 width=8) -> Append (cost=0.00..1362834.03 rows=42123961 width=32) -> Seq Scan on drop_me (cost=0.00..730974.60 rows=42123960 width=32) -> Subquery Scan on "*SELECT* 2" (cost=0.00..0.02 rows=1 width=32) -> Result (cost=0.00..0.01 rows=1 width=4) (5 rows) That's not to say your patch doesn't have merit - but maybe just not a fix for this particular case. As before, if the SQL is tweaked to align the types for the UNION, you get a parallel plan: test=# explain select count(*) from (select n1 from drop_me union all values(1::numeric)) ua; QUERY PLAN ---------------------------------------------------------------------------------------------------- Finalize Aggregate (cost=821152.71..821152.72 rows=1 width=8) -> Gather (cost=821152.50..821152.71 rows=2 width=8) Workers Planned: 2 -> Partial Aggregate (cost=820152.50..820152.51 rows=1 width=8) -> Parallel Append (cost=0.00..747235.71 rows=29166714 width=0) -> Result (cost=0.00..0.01 rows=1 width=0) -> Parallel Seq Scan on drop_me (cost=0.00..601402.13 rows=29166713 width=0) (7 rows) Regards, Greg Nancarrow Fujitsu Australia
On 24-11-2020 01:44, Greg Nancarrow wrote: > On Tue, Nov 24, 2020 at 2:34 AM Luc Vlaming <luc@swarm64.com> wrote: >> >> Hi, >> >> For this problem there is a patch I created, which is registered under >> https://commitfest.postgresql.org/30/2787/ that should fix this without >> any workarounds. Maybe someone can take a look at it? >> > > I tried your patch with the latest PG source code (24/11), but > unfortunately a non-parallel plan was still produced in this case. > > test=# explain > select count(*) > from (select > n1 > from drop_me > union all > values(1)) ua; > QUERY PLAN > -------------------------------------------------------------------------------- > Aggregate (cost=1889383.54..1889383.55 rows=1 width=8) > -> Append (cost=0.00..1362834.03 rows=42123961 width=32) > -> Seq Scan on drop_me (cost=0.00..730974.60 rows=42123960 width=32) > -> Subquery Scan on "*SELECT* 2" (cost=0.00..0.02 rows=1 width=32) > -> Result (cost=0.00..0.01 rows=1 width=4) > (5 rows) > > > That's not to say your patch doesn't have merit - but maybe just not a > fix for this particular case. > > As before, if the SQL is tweaked to align the types for the UNION, you > get a parallel plan: > > test=# explain > select count(*) > from (select > n1 > from drop_me > union all > values(1::numeric)) ua; > QUERY PLAN > ---------------------------------------------------------------------------------------------------- > Finalize Aggregate (cost=821152.71..821152.72 rows=1 width=8) > -> Gather (cost=821152.50..821152.71 rows=2 width=8) > Workers Planned: 2 > -> Partial Aggregate (cost=820152.50..820152.51 rows=1 width=8) > -> Parallel Append (cost=0.00..747235.71 rows=29166714 width=0) > -> Result (cost=0.00..0.01 rows=1 width=0) > -> Parallel Seq Scan on drop_me > (cost=0.00..601402.13 rows=29166713 width=0) > (7 rows) > > > Regards, > Greg Nancarrow > Fujitsu Australia > Hi, You're completely right, sorry for my error. I was too quick on assuming my patch would work for this specific case too; I should have tested that before replying. It looked very similar but turns out to not work because of the upper rel not being considered parallel. I would like to extend my patch to support this, or create a second patch. This would however be significantly more involved because it would require that we (always?) consider two paths whenever we process a subquery: the best parallel plan and the best serial plan. Before I emback on such a journey I would like some input on whether this would be a very bad idea. Thoughts? Regards, Luc Swarm64
On Wed, Nov 25, 2020 at 6:43 PM Luc Vlaming <luc@swarm64.com> wrote:
>
>
> You're completely right, sorry for my error. I was too quick on assuming
> my patch would work for this specific case too; I should have tested
> that before replying. It looked very similar but turns out to not work
> because of the upper rel not being considered parallel.
>
> I would like to extend my patch to support this, or create a second
> patch. This would however be significantly more involved because it
> would require that we (always?) consider two paths whenever we process a
> subquery: the best parallel plan and the best serial plan. Before I
> emback on such a journey I would like some input on whether this would
> be a very bad idea. Thoughts?
>
Hi,
I must admit, your intended approach isn't what immediately came to mind when I saw this issue. Have you analyzed and debugged this to know exactly what is going on?
I haven't had time to debug this and see exactly where the code paths diverge for the use of "values(1)" verses "values(1::numeric)" in this case, but that would be one of the first steps.
What I wondered (and I may well be wrong) was how come the documented type resolution algorithm (https://www.postgresql.org/docs/13/typeconv-union-case.html) doesn't seem to be working quite right here, at least to the point of creating the same/similar parse tree as when I change "values(1)" to "values(1::numeric)" or even just "values(1.)"? So shouldn't then the use of "values(1)" in this case (a constant, convertible to numeric - the preferred type ) result in the same (parallel) plan as when "values(1::numeric)" is used? Perhaps this isn't happening because the code is treating these as generalised expressions when their types aren't the same, and this then affects parsing/planning?
My natural thought was that there seems to be a minor issue in the code, which should be reasonably easy to fix, at least for this fairly simple case.
However, I claim no expertise in the area of parser/analyzer/planner, I only know certain areas of that code, but enough to appreciate it is complex and intricate, and easily broken.
Perhaps one of the major contributors to this area of the code, who probably know this code very well, like maybe Tom Lane or Robert Haas (to name two) might like to comment on whether what we're looking at is indeed a bug/deficiency and worth fixing, and whether Luc is correct in his expressed approach on what would be required to fix it?
Regards,
Greg Nancarrow
Fujitsu Australia
On 25-11-2020 14:54, Greg Nancarrow wrote: > > > On Wed, Nov 25, 2020 at 6:43 PM Luc Vlaming <luc@swarm64.com > <mailto:luc@swarm64.com>> wrote: > > > > > > You're completely right, sorry for my error. I was too quick on assuming > > my patch would work for this specific case too; I should have tested > > that before replying. It looked very similar but turns out to not work > > because of the upper rel not being considered parallel. > > > > I would like to extend my patch to support this, or create a second > > patch. This would however be significantly more involved because it > > would require that we (always?) consider two paths whenever we process a > > subquery: the best parallel plan and the best serial plan. Before I > > emback on such a journey I would like some input on whether this would > > be a very bad idea. Thoughts? > > > > Hi, > > I must admit, your intended approach isn't what immediately came to mind > when I saw this issue. Have you analyzed and debugged this to know > exactly what is going on? > I haven't had time to debug this and see exactly where the code paths > diverge for the use of "values(1)" verses "values(1::numeric)" in this > case, but that would be one of the first steps. > > What I wondered (and I may well be wrong) was how come the documented > type resolution algorithm > (https://www.postgresql.org/docs/13/typeconv-union-case.html > <https://www.postgresql.org/docs/13/typeconv-union-case.html>) doesn't > seem to be working quite right here, at least to the point of creating > the same/similar parse tree as when I change "values(1)" to > "values(1::numeric)" or even just "values(1.)"? So shouldn't then the > use of "values(1)" in this case (a constant, convertible to numeric - > the preferred type ) result in the same (parallel) plan as when > "values(1::numeric)" is used? Perhaps this isn't happening because the > code is treating these as generalised expressions when their types > aren't the same, and this then affects parsing/planning? > My natural thought was that there seems to be a minor issue in the code, > which should be reasonably easy to fix, at least for this fairly simple > case. > > However, I claim no expertise in the area of parser/analyzer/planner, I > only know certain areas of that code, but enough to appreciate it is > complex and intricate, and easily broken. > Perhaps one of the major contributors to this area of the code, who > probably know this code very well, like maybe Tom Lane or Robert Haas > (to name two) might like to comment on whether what we're looking at is > indeed a bug/deficiency and worth fixing, and whether Luc is correct in > his expressed approach on what would be required to fix it? > > Regards, > Greg Nancarrow > Fujitsu Australia So from what I recall from building the patch is that the difference is that when all types are identical, then flatten_simple_union_all simply flattens all union-all operations into an append relation. If you don't have identical types then the situation has to be handled by the code in prepunion.c which doesn't always keep a parallel path around. The patch I had posted fixes this for a relatively simple issue and not the case described here. If interesting I can make a draft of what this would look like if this makes it easier to discuss? Regards, Luc Swarm64
On Thu, Nov 26, 2020 at 6:11 PM Luc Vlaming <luc@swarm64.com> wrote: > > If interesting I can make a draft of what this would look like if this > makes it easier to discuss? > Sure, that would help clarify it. I did debug this a bit, but it seems my gut feeling was wrong, even though it knows a type coercion is required and can be done, the parse/analyze code doesn't actually modify the nodes in place "for fear of changing the semantics", so when the types don't exactly match it's all left up to the planner, but for this parse tree it fails to produce a parallel plan. Regards, Greg Nancarrow Fujitsu Australia
On 27-11-2020 04:14, Greg Nancarrow wrote: > On Thu, Nov 26, 2020 at 6:11 PM Luc Vlaming <luc@swarm64.com> wrote: >> >> If interesting I can make a draft of what this would look like if this >> makes it easier to discuss? >> > > Sure, that would help clarify it. Okay. I will try to build an example but this will take a few weeks as vacations and such are coming up too. > > I did debug this a bit, but it seems my gut feeling was wrong, even > though it knows a type coercion is required and can be done, the > parse/analyze code doesn't actually modify the nodes in place "for > fear of changing the semantics", so when the types don't exactly match > it's all left up to the planner, but for this parse tree it fails to > produce a parallel plan. > Yes. However I think here also lies an opportunity, because to me it seems much more appealing to have the planner being able to deal correctly with all the situations rather than having things like flatten_simple_union_all() that provide a solution for the ideal case. > Regards, > Greg Nancarrow > Fujitsu Australia > Regards, Luc Swarm64