Обсуждение: Unique index prohibits partial aggregates

Поиск
Список
Период
Сортировка

Unique index prohibits partial aggregates

От
"Bos, Fred"
Дата:

Dear pgsql-general,

 

I am currently working in PostgreSQL 13.7, compiled by Visual C++

build 1914, 64-bit and encountered the following:

 

I have a table with two columns, which is created as

follows:

 

CREATE TABLE IF NOT EXISTS bhload_nohyp_noin (

t BIGINT NOT NULL,

v REAL NULL

);

 

The table is 32 million rows long.

When I run the following query:

 

    SELECT t/(1000*3600*24) as time,

    avg(v)

    FROM bhload_nohyp_noin

    GROUP BY time

    ORDER BY time

 

Postgres executes the following plan:

 

"QUERY PLAN"

"Finalize GroupAggregate  (cost=295097.60..295226.53 rows=200 width=16)"

"  Output: ((t / 86400000)), avg(v)"

"  Group Key: ((bhload_nohyp_noin.t / 86400000))"

"  ->  Gather Merge  (cost=295097.60..295218.53 rows=1000 width=40)"

"        Output: ((t / 86400000)), (PARTIAL avg(v))"

"        Workers Planned: 5"

"        ->  Sort  (cost=295097.52..295098.02 rows=200 width=40)"

"              Output: ((t / 86400000)), (PARTIAL avg(v))"

"              Sort Key: ((bhload_nohyp_noin.t / 86400000))"

"              ->  Partial HashAggregate  (cost=295087.38..295089.88 rows=200 width=40)"

"                    Output: ((t / 86400000)), PARTIAL avg(v)"

"                    Group Key: (bhload_nohyp_noin.t / 86400000)"

"                    ->  Parallel Seq Scan on public.bhload_nohyp_noin  (cost=0.00..259966.13 rows=7024250 width=12)"

"                          Output: (t / 86400000), v"

"Settings: effective_cache_size = '128GB', force_parallel_mode = 'on', max_parallel_workers = '40',

max_parallel_workers_per_gather = '10', parallel_setup_cost = '0', random_page_cost = '1.1',

temp_buffers = '200MB', work_mem = '1GB'"

"Planning:"

"  Buffers: shared hit=14"

"Planning Time: 0.747 ms"

 

This plan takes about 2 seconds.

However, when postgres learns the table statistics, either after running the

query once, performing vacuum analyse on the table or assigning a unique index,

postgres refuses to perform partial aggregates. The reason is that the column

t is unique and ordered which causes the query plan to change.

This results in the following query plan:

 

"QUERY PLAN"

"GroupAggregate  (cost=971690.95..5460155.25 rows=31850064 width=16)"

"  Output: ((t / 86400000)), avg(v)"

"  Group Key: ((bhload_nohyp_noin.t / 86400000))"

"  ->  Gather Merge  (cost=971690.95..4823153.97 rows=31850064 width=12)"

"        Output: ((t / 86400000)), v"

"        Workers Planned: 5"

"        ->  Sort  (cost=971690.88..987615.91 rows=6370013 width=12)"

"              Output: ((t / 86400000)), v"

"              Sort Key: ((bhload_nohyp_noin.t / 86400000))"

"              ->  Parallel Seq Scan on public.bhload_nohyp_noin  (cost=0.00..251788.16 rows=6370013 width=12)"

"                    Output: (t / 86400000), v"

"Settings: effective_cache_size = '128GB', force_parallel_mode = 'on', max_parallel_workers = '40',

max_parallel_workers_per_gather = '10', parallel_setup_cost = '0', random_page_cost = '1.1',

temp_buffers = '200MB', work_mem = '1GB'"

"Planning:"

"  Buffers: shared hit=6"

"Planning Time: 0.180 ms"

 

This takes about 18 seconds, which is much slower. When swapping the columns t

and v, so grouping by v, postgres decides to do partial aggregation again

which makes the operation 2 seconds. 

 

Some extra notes:

 

- I noticed that the parallel and partial hash aggregation gives the speed up.

- I tried to trigger it by using the parallel costs, but I could not trigger it.

- The table statistics before vacuum analyze is empty.

- Here are the table statistics after vacuum analyze:

 

schemaname    tablename          attname    inherited    null_frac    avg_width    n_distinct    correlation    most_common_elem_freqs    elem_count_histogram

public        bhload_nohyp_noin    t        FALSE        0                8            -1            1                   NULL                 NULL

public        bhload_nohyp_noin    v        FALSE        0                4           3937        -0.20693               NULL                 NULL

 

 

I expected the query to become faster

with a unique index or column, so why does the query planner decide on group

aggregation instead of partial aggregation?

 

Regards,

Fred Bos

---------------------------------------------------------------------------
The contents of this e-mail (including any attachments) are for the intended recipients only. If you are not an intended recipient but have received this email in error, we kindly request you to inform the sender of such error and delete this email and any attachments. If you open any attachments of this email, please understand that you do so at your own risk. We have made all reasonable efforts to keep this email and its attachments free from any bugs, viruses or the like, but cannot accept any responsibility for it.
Huisman Equipment B.V. and its affiliated companies cannot take any responsibility with regard to the accuracy or completeness of the content of this email and any attachments. Please note that this email and any attachments may contain information that is considered confidential, privileged and subject to copyright or other intellectual property rights. We kindly request (and insofar legally possible, demand) you to keep the content of this email and any attachments confidential and abide to the restrictions following from such protection.
---------------------------------------------------------------------------

Re: Unique index prohibits partial aggregates

От
David Rowley
Дата:
On Mon, 27 Jun 2022 at 23:49, Bos, Fred <fbos@huisman-nl.com> wrote:
> "Settings: effective_cache_size = '128GB', force_parallel_mode = 'on', max_parallel_workers = '40',

You shouldn't be using force_parallel_mode. It does not do what you
think. See the documentation for that GUC, or read [1]

> I expected the query to become faster
> with a unique index or column, so why does the query planner decide on group
> aggregation instead of partial aggregation?

It just simply does not know how many groups are likely to exists on
your expression.  Statistics are only gathered on bare columns. The
planner has no idea how many groups are likely to exist for
"t/(1000*3600*24)".

In PostgreSQL 14 and above you could create extended statistics for
the expression using:

create statistics t1_t_stats (ndistinct) on (t/(1000*3600*24)) from
bhload_nohyp_noin; -- pg14
analyze bhload_nohyp_noin;

for your version (pg13), you'd need to create an expression index.

create index on bhload_nohyp_noin using brin ((t/(1000*3600*24)));
analyze bhload_nohyp_noin;

I added "using brin" as a brin index is going to be a bit more
lightweight than a btree index.  You only need the index to instruct
ANALYZE to gather statistics. You might also want to ramp up the pages
per range.

With that, the planner might then realise that parallel aggregate
might be worthwhile.

David

[1] https://www.enterprisedb.com/postgres-tutorials/using-forceparallelmode-correctly-postgresql



RE: Unique index prohibits partial aggregates

От
"Bos, Fred"
Дата:
Thank you for your reply.

I turned force_parallel_mode off.

Adding a BRIN index on this expression does indeed improve the performance for
this particular expression (it triggers the faster partial hash aggregate
again). However, my expression is variable, so "t/(1000*3600*24)" actually
is "t/dt", where dt changes frequently. When dt is changed, the query planner
reverts to GroupAggregate again (because the BRIN index is not suitable).

Also, when postgres doesn't know anything about the table, just after
importing, it has shown that it is capable of doing the partial hash
aggregate operation on the same table very quickly.

To elaborate on this:

If I setup the table with this query,

        CREATE TABLE IF NOT EXISTS table (
                t BIGINT NOT NULL,
                v REAL NULL
        );

then insert the rows,

        INSERT INTO table .....;
                about 30million rows,

and then directly execute the query,

        SELECT t/dt as time, avg(v)
        FROM table
        GROUP BY time
        ORDER BY time;

the query executes in under 2 seconds for any dt.

However, the query runs in about 20 seconds when I do the any of following:

   - VACCUM ANALYZE and, then running the same query,
   - or, CREATE UNIQUE INDEX ON table USING btree (t), and then running the
     same query.

So it is possible to run the query much faster but postgres won't do it.

Is this because when the statistics are unknown, it expects a relatively low
amount of groups and opts for a partial plan?

Finally, is there a way to force postgres to do the partial hash aggregate,
either by changing a setting or by influencing the expected amount of output
groups for each query?

Regards,
Fred

---------------------------------------------------------------------------
The contents of this e-mail (including any attachments) are for the intended recipients only. If you are not an
intendedrecipient but have received this email in error, we kindly request you to inform the sender of such error and
deletethis email and any attachments. If you open any attachments of this email, please understand that you do so at
yourown risk. We have made all reasonable efforts to keep this email and its attachments free from any bugs, viruses or
thelike, but cannot accept any responsibility for it. 
Huisman Equipment B.V. and its affiliated companies cannot take any responsibility with regard to the accuracy or
completenessof the content of this email and any attachments. Please note that this email and any attachments may
containinformation that is considered confidential, privileged and subject to copyright or other intellectual property
rights.We kindly request (and insofar legally possible, demand) you to keep the content of this email and any
attachmentsconfidential and abide to the restrictions following from such protection. 
---------------------------------------------------------------------------



Re: Unique index prohibits partial aggregates

От
David Rowley
Дата:
On Wed, 29 Jun 2022 at 00:45, Bos, Fred <fbos@huisman-nl.com> wrote:
> Finally, is there a way to force postgres to do the partial hash aggregate,
> either by changing a setting or by influencing the expected amount of output
> groups for each query?

You could do something like:

ALTER TABLE bhload_nohyp_noin ALTER COLUMN t SET (n_distinct = 200);
ANALYZE bhload_nohyp_noin;

Please be aware that this may have detrimental effects if you do any
joins or group bys directly on this column.  Otherwise, providing you
don't have a unique index on that column, then it should trick the
planner into thinking there will be fewer groups than it currently
thinks there will be, which will likely result in the parallel plan
that you desire.

David