0
votes

I'm running this query in our database:

select
(
  select least(2147483647, sum(pb.nr_size)) 
  from tb_pr_dc pd
  inner join tb_pr_dc_bn pb on 1=1
    and pb.id_pr_dc_bn = pd.id_pr_dc_bn 
  where 1=1
    and pd.id_pr = pt.id_pr -- outer query column
) 
from 
(
  select regexp_split_to_table('[list of 500 ids]', ',')::integer id_pr 
) pt 
;

Which outputs 500 rows having a single result column and takes around 1 min and 43 secs to run. The explain (analyze, verbose, buffers) outputs the following plan:

Subquery Scan on pt  (cost=0.00..805828.19 rows=1000 width=8) (actual time=96.791..103205.872 rows=500 loops=1)
  Output: (SubPlan 1)
  Buffers: shared hit=373771 read=153484
  ->  Result  (cost=0.00..22.52 rows=1000 width=4) (actual time=0.434..3.729 rows=500 loops=1)
        Output: ((regexp_split_to_table('[list of 500 ids]', ',')::integer id_pr)
        ->  ProjectSet  (cost=0.00..5.02 rows=1000 width=32) (actual time=0.429..2.288 rows=500 loops=1)
              Output: (regexp_split_to_table('[list of 500 ids]', ',')::integer id_pr
              ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
  SubPlan 1
    ->  Aggregate  (cost=805.78..805.80 rows=1 width=8) (actual time=206.399..206.400 rows=1 loops=500)
          Output: LEAST('2147483647'::bigint, sum((pb.nr_size)::integer))
          Buffers: shared hit=373771 read=153484
          ->  Nested Loop  (cost=0.87..805.58 rows=83 width=4) (actual time=1.468..206.247 rows=219 loops=500)
                Output: pb.nr_size
                Inner Unique: true
                Buffers: shared hit=373771 read=153484
                ->  Index Scan using tb_pr_dc_in05 on db.tb_pr_dc pd  (cost=0.43..104.02 rows=83 width=4) (actual time=0.233..49.289 rows=219 loops=500)
                      Output: pd.id_pr_dc, pd.ds_pr_dc, pd.id_pr, pd.id_user_in, pd.id_user_ex, pd.dt_in, pd.dt_ex, pd.ds_mt_ex, pd.in_at, pd.id_tp_pr_dc, pd.id_pr_xz (...)
                      Index Cond: ((pd.id_pr)::integer = pt.id_pr)
                      Buffers: shared hit=24859 read=64222
                ->  Index Scan using tb_pr_dc_bn_pk on db.tb_pr_dc_bn pb  (cost=0.43..8.45 rows=1 width=8) (actual time=0.715..0.715 rows=1 loops=109468)
                      Output: pb.id_pr_dc_bn, pb.ds_ex, pb.ds_md_dc, pb.ds_m5_dc, pb.nm_aq, pb.id_user, pb.dt_in, pb.ob_pr_dc, pb.nr_size, pb.ds_sg, pb.ds_cr_ch, pb.id_user_ (...)
                      Index Cond: ((pb.id_pr_dc_bn)::integer = (pd.id_pr_dc_bn)::integer)
                      Buffers: shared hit=348912 read=89262
Planning Time: 1.151 ms
Execution Time: 103206.243 ms

The logic is: for each id_pr chosen (in the list of 500 ids) calculate the sum of the integer column pb.nr_size associated with them, returning the lesser value between this amount and the number 2,147,483,647. The result must contain 500 rows, one for each id, and we already know that they'll match at least one row in the subquery, so will not produce null values.

The index tb_pr_dc_in05 is a b-tree on id_pr only, which is of integer type. The index tb_pr_dc_bn_pk is a b-tree on the primary key id_pr_dc_bn only, which is of integer type also. Table tb_pr_dc has many rows for each id_pr. Actually, we have 209,217 unique id_prs in tb_pr_dc for a total of 13,910,855 rows. Table tb_pr_dc_bn has the same amount of rows.

As can be seen, we defined 500 ids to query tb_pr_dc, finding 109,468 rows (less than 1% of the table size) and then finding the same amount looking in tb_pr_dc_bn. Imo, the indexes look fine and the amount of rows to evaluate is minimal, so I can't understand why it's taking so much time to run this query. A lot of other queries reading a lot more of data on other tables and doing more calculations are running fine. The DBA just ran a reindex and vacuum analyze, but still it's running the same slow way. We are running PostgreSQL 11 on Linux. I'm running this query in a replica without concurrent access.

What could I be missing that could improve this query performance?

Thanks for your attention.

1
Sample data, desired results, and a clear explanation of the logic would help. - Gordon Linoff
You need to show us the table and index definitions, as well as row counts for each of the tables. Maybe your tables are defined poorly. Maybe the indexes aren't created correctly. Maybe you don't have an index on that column you thought you did. Without seeing the table and index definitions, we can't tell. We need row counts because that can affect query planning. - Andy Lester
What is least(2147483647, sum(pb.nr_size)) supposed to be doing? Are you trying to assign a value if the SUM comes up NULL? COALESCE is the right tool to use for that. - Andy Lester
I'm editting to explain the logic of the query. - Felypp Oliveira
Why the sub-query? You can use the list directly in the where clause. And why the useless 1=1 conditions? I think you can simplify the query to something like this. An index on tb_pr_dc (id_pr, id_pr_dc_bn) and tb_pr_dc_bn (id_pr_dc_bn) should help - a_horse_with_no_name

1 Answers

1
votes

The time is spent jumping all over the table to find 109468 randomly scattered rows, issuing random IO requests to do so. You can verify that be turning track_io_timing on and redoing the plans (probably just leave it turned on globally and by default, the overhead is low and the value it produces is high), but I'm sure enough that I don't need to see that output before reaching this conclusion. The other queries that are faster are probably accessing fewer disk pages because they access data that is more tightly packed, or is organized so that it can be read more sequentially. In fact, I would say your query is quite fast given how many pages it had to read.

You ask about why so many columns are output in the internal nodes of the plan. The reason for that is that PostgreSQL often just passes around pointers to where the tuple lives in the shared_buffers, and the tuple being pointed to has the columns that the table itself has. It could allocate memory in which to store a reformatted version of the tuple with the unnecessary columns stripped out, but that would generally be more work, not less. If it was a reason to copy and re-form the tuple anyway, it will remove the extraneous columns while it does so. But it won't do it without a reason.

One way to sped this up is to create indexes which will enable index-only scans. Those would be on tb_pr_dc (id_pr, id_pr_dc_bn) and on tb_pr_dc_bn (id_pr_dc_bn, nr_size).

If this isn't enough, there might be other ways to improve this too; but I can't think through them if I keep getting distracted by the long strings of unmemorable unpronounceable gibberish you have for table and column names.