Thread: Planning time is time-consuming
Hello,
I have three tables:
- test_db_bench_1
- test_db_bench_tenants
- test_db_bench_tenant_closure
And the query to join them:
SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
FROM "test_db_bench_1"
JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
AND ("tenants_child"."is_deleted" != true))
JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
AND ("tenants_closure"."barrier" <= 0))
JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330'))
AND ("tenants_parent"."is_deleted" != true))
LIMIT 1
With following execution plan:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1)
-> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1)
-> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1)
-> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1)
-> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1)
Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed)
Index Cond: (parent_id = tenants_parent.id)
Filter: (barrier <= 0)
-> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31 rows=1 width=44) (never executed)
Index Cond: (id = tenants_closure.child_id)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44) (never executed)
Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
Planning Time: 0.732 ms
Execution Time: 0.039 ms
Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution.
Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time.
What I have tried:
- disabled JIT, which resulted in a minor improvement, around 5 microseconds.
- disabled constraint_exclusion, which also didn't have a significant impact.
Sizes of tables and indexes:
-- test_db_bench_1
List of relations
Schema | Name | Type | Owner | Persistence | Access method | Size | Description
--------+-----------------+-------+--------+-------------+---------------+---------+-------------
public | test_db_bench_1 | table | dbuser | permanent | heap | 5351 MB |
Column | Type | Collation | Nullable | Default
---------------------------+------------------------+-----------+----------+----------------------------------------------
------
id | bigint | | not null | nextval('test_db_bench_1_id_seq'::regclass)
uuid | uuid | | not null |
checksum | character varying(64) | | not null |
tenant_id | character varying(36) | | not null |
cti_entity_uuid | character varying(36) | | |
euc_id | character varying(64) | | not null |
workflow_id | bigint | | |
state | integer | | not null |
type | character varying(64) | | not null |
queue | character varying(64) | | not null |
priority | integer | | not null |
issuer_id | character varying(64) | | not null |
issuer_cluster_id | character varying(64) | | |
heartbeat_ivl_str | character varying(64) | | |
heartbeat_ivl_ns | bigint | | |
queue_timeout_str | character varying(64) | | |
queue_timeout_ns | bigint | | |
ack_timeout_str | character varying(64) | | |
ack_timeout_ns | bigint | | |
exec_timeout_str | character varying(64) | | |
exec_timeout_ns | bigint | | |
life_time_str | character varying(64) | | |
life_time_ns | bigint | | |
max_assign_count | integer | | not null |
assign_count | integer | | not null |
max_fail_count | integer | | not null |
fail_count | integer | | not null |
cancellable | boolean | | not null |
cancel_requested | boolean | | not null |
blocker_count | integer | | not null |
started_by_user | character varying(256) | | |
policy_id | character varying(64) | | |
policy_type | character varying(64) | | |
policy_name | character varying(256) | | |
resource_id | character varying(64) | | |
resource_type | character varying(64) | | |
resource_name | character varying(256) | | |
tags | text | | |
affinity_agent_id | character varying(64) | | not null |
affinity_cluster_id | character varying(64) | | not null |
argument | bytea | | |
context | bytea | | |
progress | integer | | |
progress_total | integer | | |
assigned_agent_id | character varying(64) | | |
assigned_agent_cluster_id | character varying(64) | | |
enqueue_time_str | character varying(64) | | |
enqueue_time_ns | bigint | | not null |
assign_time_str | character varying(64) | | |
assign_time_ns | bigint | | |
start_time_str | character varying(64) | | |
start_time_ns | bigint | | |
update_time_str | character varying(64) | | not null |
update_time_ns | bigint | | not null |
completion_time_str | character varying(64) | | |
completion_time_ns | bigint | | |
result_code | integer | | |
result_error | bytea | | |
result_warnings | bytea | | |
result_payload | bytea | | |
const_val | integer | | |
Indexes:
"test_db_bench_1_pkey" PRIMARY KEY, btree (id)
"test_db_bench_1_idx_completion_time_ns_1" btree (completion_time_ns)
"test_db_bench_1_idx_cti_entity_uuid_2" btree (cti_entity_uuid)
"test_db_bench_1_idx_enqueue_time_ns_10" btree (enqueue_time_ns)
"test_db_bench_1_idx_euc_id_4" btree (euc_id)
"test_db_bench_1_idx_policy_id_12" btree (policy_id)
"test_db_bench_1_idx_queue_18" btree (queue, type, tenant_id)
"test_db_bench_1_idx_queue_19" btree (queue, type, euc_id)
"test_db_bench_1_idx_queue_5" btree (queue, state, affinity_agent_id, affinity_cluster_id, tenant_id, priority)
"test_db_bench_1_idx_queue_6" btree (queue, state, affinity_agent_id, affinity_cluster_id, euc_id, priority)
"test_db_bench_1_idx_resource_id_11" btree (resource_id)
"test_db_bench_1_idx_resource_id_14" btree (resource_id, enqueue_time_ns)
"test_db_bench_1_idx_result_code_13" btree (result_code)
"test_db_bench_1_idx_start_time_ns_9" btree (start_time_ns)
"test_db_bench_1_idx_state_8" btree (state, completion_time_ns)
"test_db_bench_1_idx_tenant_id_3" btree (tenant_id)
"test_db_bench_1_idx_type_15" btree (type)
"test_db_bench_1_idx_type_16" btree (type, tenant_id, enqueue_time_ns)
"test_db_bench_1_idx_type_17" btree (type, euc_id, enqueue_time_ns)
"test_db_bench_1_idx_update_time_ns_7" btree (update_time_ns)
"test_db_bench_1_idx_uuid_0" btree (uuid)
"test_db_bench_1_uuid_key" UNIQUE CONSTRAINT, btree (uuid)
-- test_db_bench_tenants
Schema | Name | Type | Owner | Persistence | Access method | Size | Description
--------+-----------------------+-------+--------+-------------+---------------+---------+-------------
public | test_db_bench_tenants | table | dbuser | permanent | heap | 8432 kB |
Column | Type | Collation | Nullable | Default
-------------------+------------------------+-----------+----------+---------
id | bigint | | not null |
uuid | character varying(36) | | not null |
name | character varying(255) | | not null |
kind | character(1) | | not null |
is_deleted | boolean | | not null | false
parent_id | bigint | | not null |
parent_has_access | boolean | | not null | true
nesting_level | smallint | | not null |
Indexes:
"test_db_bench_tenants_pkey" PRIMARY KEY, btree (id)
"test_db_bench_tenants_uuid" UNIQUE CONSTRAINT, btree (uuid)
-- test_db_bench_tenant_closure
Schema | Name | Type | Owner | Persistence | Access method | Size | Description
--------+------------------------------+-------+--------+-------------+---------------+-------+-------------
public | test_db_bench_tenant_closure | table | dbuser | permanent | heap | 22 MB |
Column | Type | Collation | Nullable | Default
-------------+--------------+-----------+----------+---------
parent_id | bigint | | not null |
child_id | bigint | | not null |
parent_kind | character(1) | | not null |
barrier | smallint | | not null | 0
Indexes:
"test_db_bench_tenant_closure_pkey" PRIMARY KEY, btree (parent_id, child_id)
"cybercache_tenants_closure_child_id_idx" btree (child_id)
Postgresql version: 15.3 (Debian 15.3-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
And just in case it matters, this is an experimental setup, so Postgresql running in Docker.
Thank you.
--
Mikhail
I have three tables:
- test_db_bench_1
- test_db_bench_tenants
- test_db_bench_tenant_closure
And the query to join them:
SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
FROM "test_db_bench_1"
JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
AND ("tenants_child"."is_deleted" != true))
JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
AND ("tenants_closure"."barrier" <= 0))
JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330'))
AND ("tenants_parent"."is_deleted" != true))
LIMIT 1
With following execution plan:
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1)
-> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1)
-> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1)
-> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1)
-> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1)
Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed)
Index Cond: (parent_id = tenants_parent.id)
Filter: (barrier <= 0)
-> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31 rows=1 width=44) (never executed)
Index Cond: (id = tenants_closure.child_id)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44) (never executed)
Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
Planning Time: 0.732 ms
Execution Time: 0.039 ms
Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution.
Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time.
What I have tried:
- disabled JIT, which resulted in a minor improvement, around 5 microseconds.
- disabled constraint_exclusion, which also didn't have a significant impact.
Sizes of tables and indexes:
-- test_db_bench_1
List of relations
Schema | Name | Type | Owner | Persistence | Access method | Size | Description
--------+-----------------+-------+--------+-------------+---------------+---------+-------------
public | test_db_bench_1 | table | dbuser | permanent | heap | 5351 MB |
Column | Type | Collation | Nullable | Default
---------------------------+------------------------+-----------+----------+----------------------------------------------
------
id | bigint | | not null | nextval('test_db_bench_1_id_seq'::regclass)
uuid | uuid | | not null |
checksum | character varying(64) | | not null |
tenant_id | character varying(36) | | not null |
cti_entity_uuid | character varying(36) | | |
euc_id | character varying(64) | | not null |
workflow_id | bigint | | |
state | integer | | not null |
type | character varying(64) | | not null |
queue | character varying(64) | | not null |
priority | integer | | not null |
issuer_id | character varying(64) | | not null |
issuer_cluster_id | character varying(64) | | |
heartbeat_ivl_str | character varying(64) | | |
heartbeat_ivl_ns | bigint | | |
queue_timeout_str | character varying(64) | | |
queue_timeout_ns | bigint | | |
ack_timeout_str | character varying(64) | | |
ack_timeout_ns | bigint | | |
exec_timeout_str | character varying(64) | | |
exec_timeout_ns | bigint | | |
life_time_str | character varying(64) | | |
life_time_ns | bigint | | |
max_assign_count | integer | | not null |
assign_count | integer | | not null |
max_fail_count | integer | | not null |
fail_count | integer | | not null |
cancellable | boolean | | not null |
cancel_requested | boolean | | not null |
blocker_count | integer | | not null |
started_by_user | character varying(256) | | |
policy_id | character varying(64) | | |
policy_type | character varying(64) | | |
policy_name | character varying(256) | | |
resource_id | character varying(64) | | |
resource_type | character varying(64) | | |
resource_name | character varying(256) | | |
tags | text | | |
affinity_agent_id | character varying(64) | | not null |
affinity_cluster_id | character varying(64) | | not null |
argument | bytea | | |
context | bytea | | |
progress | integer | | |
progress_total | integer | | |
assigned_agent_id | character varying(64) | | |
assigned_agent_cluster_id | character varying(64) | | |
enqueue_time_str | character varying(64) | | |
enqueue_time_ns | bigint | | not null |
assign_time_str | character varying(64) | | |
assign_time_ns | bigint | | |
start_time_str | character varying(64) | | |
start_time_ns | bigint | | |
update_time_str | character varying(64) | | not null |
update_time_ns | bigint | | not null |
completion_time_str | character varying(64) | | |
completion_time_ns | bigint | | |
result_code | integer | | |
result_error | bytea | | |
result_warnings | bytea | | |
result_payload | bytea | | |
const_val | integer | | |
Indexes:
"test_db_bench_1_pkey" PRIMARY KEY, btree (id)
"test_db_bench_1_idx_completion_time_ns_1" btree (completion_time_ns)
"test_db_bench_1_idx_cti_entity_uuid_2" btree (cti_entity_uuid)
"test_db_bench_1_idx_enqueue_time_ns_10" btree (enqueue_time_ns)
"test_db_bench_1_idx_euc_id_4" btree (euc_id)
"test_db_bench_1_idx_policy_id_12" btree (policy_id)
"test_db_bench_1_idx_queue_18" btree (queue, type, tenant_id)
"test_db_bench_1_idx_queue_19" btree (queue, type, euc_id)
"test_db_bench_1_idx_queue_5" btree (queue, state, affinity_agent_id, affinity_cluster_id, tenant_id, priority)
"test_db_bench_1_idx_queue_6" btree (queue, state, affinity_agent_id, affinity_cluster_id, euc_id, priority)
"test_db_bench_1_idx_resource_id_11" btree (resource_id)
"test_db_bench_1_idx_resource_id_14" btree (resource_id, enqueue_time_ns)
"test_db_bench_1_idx_result_code_13" btree (result_code)
"test_db_bench_1_idx_start_time_ns_9" btree (start_time_ns)
"test_db_bench_1_idx_state_8" btree (state, completion_time_ns)
"test_db_bench_1_idx_tenant_id_3" btree (tenant_id)
"test_db_bench_1_idx_type_15" btree (type)
"test_db_bench_1_idx_type_16" btree (type, tenant_id, enqueue_time_ns)
"test_db_bench_1_idx_type_17" btree (type, euc_id, enqueue_time_ns)
"test_db_bench_1_idx_update_time_ns_7" btree (update_time_ns)
"test_db_bench_1_idx_uuid_0" btree (uuid)
"test_db_bench_1_uuid_key" UNIQUE CONSTRAINT, btree (uuid)
-- test_db_bench_tenants
Schema | Name | Type | Owner | Persistence | Access method | Size | Description
--------+-----------------------+-------+--------+-------------+---------------+---------+-------------
public | test_db_bench_tenants | table | dbuser | permanent | heap | 8432 kB |
Column | Type | Collation | Nullable | Default
-------------------+------------------------+-----------+----------+---------
id | bigint | | not null |
uuid | character varying(36) | | not null |
name | character varying(255) | | not null |
kind | character(1) | | not null |
is_deleted | boolean | | not null | false
parent_id | bigint | | not null |
parent_has_access | boolean | | not null | true
nesting_level | smallint | | not null |
Indexes:
"test_db_bench_tenants_pkey" PRIMARY KEY, btree (id)
"test_db_bench_tenants_uuid" UNIQUE CONSTRAINT, btree (uuid)
-- test_db_bench_tenant_closure
Schema | Name | Type | Owner | Persistence | Access method | Size | Description
--------+------------------------------+-------+--------+-------------+---------------+-------+-------------
public | test_db_bench_tenant_closure | table | dbuser | permanent | heap | 22 MB |
Column | Type | Collation | Nullable | Default
-------------+--------------+-----------+----------+---------
parent_id | bigint | | not null |
child_id | bigint | | not null |
parent_kind | character(1) | | not null |
barrier | smallint | | not null | 0
Indexes:
"test_db_bench_tenant_closure_pkey" PRIMARY KEY, btree (parent_id, child_id)
"cybercache_tenants_closure_child_id_idx" btree (child_id)
Postgresql version: 15.3 (Debian 15.3-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
And just in case it matters, this is an experimental setup, so Postgresql running in Docker.
Thank you.
--
Mikhail
On Fri, 2023-09-08 at 18:51 +0800, Mikhail Balayan wrote: > I have three tables: > - test_db_bench_1 > - test_db_bench_tenants > - test_db_bench_tenant_closure > > And the query to join them: > SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id" > FROM "test_db_bench_1" > JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id") > AND ("tenants_child"."is_deleted" != true)) > JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id") > AND ("tenants_closure"."barrier" <= 0)) > JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id") > AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330')) > AND ("tenants_parent"."is_deleted" != true)) > LIMIT 1 > > > With following execution plan: > > QUERY PLAN > ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ > --------------- > Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1) > -> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1) > -> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1) > -> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1) > -> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1) > Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text) > Filter: (NOT is_deleted) > -> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed) > Index Cond: (parent_id = tenants_parent.id) > Filter: (barrier <= 0) > -> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31rows=1 width=44) (never executed) > Index Cond: (id = tenants_closure.child_id) > Filter: (NOT is_deleted) > -> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44)(never executed) > Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text) > Planning Time: 0.732 ms > Execution Time: 0.039 ms > > > Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution. > > Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time. You could try to VACUUM the involved tables; indexes with many entries pointing to dead tuples can cause a long planing time. Also, there are quite a lot of indexes on "test_db_bench_1". On a test database, drop some indexes and see if that makes a difference. Finally, check if "default_statistics_target" is set to a high value, or if the "Stats target" for some column in the "\d+ tablename" output is set higher than 100. Yours, Laurenz Albe
Also, if you write sql with bind params, planning time should be once for the sql. Subsequent sql will use cached stmt.
From: Laurenz Albe <laurenz.albe@cybertec.at>
Sent: Sunday, September 10, 2023 6:15:43 PM
To: Mikhail Balayan <mv.balayan@gmail.com>; pgsql-performance@postgresql.org <pgsql-performance@postgresql.org>
Subject: Re: Planning time is time-consuming
Sent: Sunday, September 10, 2023 6:15:43 PM
To: Mikhail Balayan <mv.balayan@gmail.com>; pgsql-performance@postgresql.org <pgsql-performance@postgresql.org>
Subject: Re: Planning time is time-consuming
On Fri, 2023-09-08 at 18:51 +0800, Mikhail Balayan wrote:
> I have three tables:
> - test_db_bench_1
> - test_db_bench_tenants
> - test_db_bench_tenant_closure
>
> And the query to join them:
> SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
> FROM "test_db_bench_1"
> JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
> AND ("tenants_child"."is_deleted" != true))
> JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
> AND ("tenants_closure"."barrier" <= 0))
> JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
> AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330'))
> AND ("tenants_parent"."is_deleted" != true))
> LIMIT 1
>
>
> With following execution plan:
>
> QUERY PLAN
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> ---------------
> Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1)
> -> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1)
> -> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1)
> -> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1)
> -> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1)
> Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed)
> Index Cond: (parent_id = tenants_parent.id)
> Filter: (barrier <= 0)
> -> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31 rows=1 width=44) (never executed)
> Index Cond: (id = tenants_closure.child_id)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44) (never executed)
> Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
> Planning Time: 0.732 ms
> Execution Time: 0.039 ms
>
>
> Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution.
>
> Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time.
You could try to VACUUM the involved tables; indexes with many entries pointing to dead tuples
can cause a long planing time.
Also, there are quite a lot of indexes on "test_db_bench_1". On a test database, drop some
indexes and see if that makes a difference.
Finally, check if "default_statistics_target" is set to a high value, or if the "Stats target"
for some column in the "\d+ tablename" output is set higher than 100.
Yours,
Laurenz Albe
> I have three tables:
> - test_db_bench_1
> - test_db_bench_tenants
> - test_db_bench_tenant_closure
>
> And the query to join them:
> SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
> FROM "test_db_bench_1"
> JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
> AND ("tenants_child"."is_deleted" != true))
> JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
> AND ("tenants_closure"."barrier" <= 0))
> JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
> AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330'))
> AND ("tenants_parent"."is_deleted" != true))
> LIMIT 1
>
>
> With following execution plan:
>
> QUERY PLAN
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> ---------------
> Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1)
> -> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1)
> -> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1)
> -> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1)
> -> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1)
> Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed)
> Index Cond: (parent_id = tenants_parent.id)
> Filter: (barrier <= 0)
> -> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31 rows=1 width=44) (never executed)
> Index Cond: (id = tenants_closure.child_id)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44) (never executed)
> Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
> Planning Time: 0.732 ms
> Execution Time: 0.039 ms
>
>
> Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution.
>
> Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time.
You could try to VACUUM the involved tables; indexes with many entries pointing to dead tuples
can cause a long planing time.
Also, there are quite a lot of indexes on "test_db_bench_1". On a test database, drop some
indexes and see if that makes a difference.
Finally, check if "default_statistics_target" is set to a high value, or if the "Stats target"
for some column in the "\d+ tablename" output is set higher than 100.
Yours,
Laurenz Albe
On 11 September 2023 03:15:43 CEST, Laurenz Albe <laurenz.albe@cybertec.at> wrote: >On Fri, 2023-09-08 at 18:51 +0800, Mikhail Balayan wrote: >> I have three tables: >> - test_db_bench_1 >> - test_db_bench_tenants >> - test_db_bench_tenant_closure >> >> And the query to join them: >> SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id" >> FROM "test_db_bench_1" >> JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id") >> AND ("tenants_child"."is_deleted" != true)) >> JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id") >> AND ("tenants_closure"."barrier" <= 0)) >> JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id") >> AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330')) >> AND ("tenants_parent"."is_deleted" != true)) >> LIMIT 1 >> >> >> With following execution plan: >> >> QUERY PLAN >> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ >> --------------- >> Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1) >> -> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1) >> -> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1) >> -> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1) >> -> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1) >> Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text) >> Filter: (NOT is_deleted) >> -> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed) >> Index Cond: (parent_id = tenants_parent.id) >> Filter: (barrier <= 0) >> -> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31rows=1 width=44) (never executed) >> Index Cond: (id = tenants_closure.child_id) >> Filter: (NOT is_deleted) >> -> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44)(never executed) >> Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text) >> Planning Time: 0.732 ms >> Execution Time: 0.039 ms >> >> >> Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution. >> >> Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time. > >You could try to VACUUM the involved tables; indexes with many entries pointing to dead tuples >can cause a long planing time. > >Also, there are quite a lot of indexes on "test_db_bench_1". On a test database, drop some >indexes and see if that makes a difference. You can use pg_stat_user_indexes to check if those indexes are in use or not. > >Finally, check if "default_statistics_target" is set to a high value, or if the "Stats target" >for some column in the "\d+ tablename" output is set higher than 100. > >Yours, >Laurenz Albe > >
Hi Laurenz,
My bad, I forgot to write that I tried vacuum too, but it didn't help. To demonstrate the result, I did it again:
# vacuum (analyze, verbose) test_db_bench_1;
INFO: vacuuming "perfkit.public.test_db_bench_1"
INFO: launched 2 parallel vacuum workers for index cleanup (planned: 2)
INFO: finished vacuuming "perfkit.public.test_db_bench_1": index scans: 0
pages: 0 removed, 684731 remain, 17510 scanned (2.56% of total)
tuples: 0 removed, 3999770 remain, 0 are dead but not yet removable
removable cutoff: 27200203, which was 0 XIDs old when operation ended
index scan bypassed: 7477 pages from table (1.09% of total) have 20072 dead item identifiers
avg read rate: 0.099 MB/s, avg write rate: 0.009 MB/s
buffer usage: 27770 hits, 11 misses, 1 dirtied
WAL usage: 1 records, 1 full page images, 1762 bytes
system usage: CPU: user: 0.15 s, system: 0.71 s, elapsed: 0.87 s
INFO: vacuuming "perfkit.pg_toast.pg_toast_16554"
INFO: finished vacuuming "perfkit.pg_toast.pg_toast_16554": index scans: 0
pages: 0 removed, 0 remain, 0 scanned (100.00% of total)
tuples: 0 removed, 0 remain, 0 are dead but not yet removable
removable cutoff: 27200203, which was 0 XIDs old when operation ended
new relfrozenxid: 27200203, which is 4000060 XIDs ahead of previous value
index scan not needed: 0 pages from table (100.00% of total) had 0 dead item identifiers removed
avg read rate: 113.225 MB/s, avg write rate: 0.000 MB/s
buffer usage: 3 hits, 1 misses, 0 dirtied
WAL usage: 1 records, 0 full page images, 188 bytes
system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
INFO: analyzing "public.test_db_bench_1"
INFO: "test_db_bench_1": scanned 30000 of 684731 pages, containing 175085 live rows and 897 dead rows; 30000 rows in sample, 3996204 estimated total rows
VACUUM
# vacuum (analyze, verbose) test_db_bench_tenants;
INFO: vacuuming "perfkit.public.test_db_bench_tenants"
INFO: launched 2 parallel vacuum workers for index cleanup (planned: 2)
INFO: finished vacuuming "perfkit.public.test_db_bench_tenants": index scans: 0
pages: 0 removed, 78154 remain, 1 scanned (0.00% of total)
tuples: 0 removed, 4064008 remain, 0 are dead but not yet removable
removable cutoff: 27200204, which was 0 XIDs old when operation ended
new relfrozenxid: 27200204, which is 2 XIDs ahead of previous value
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed
avg read rate: 0.000 MB/s, avg write rate: 0.000 MB/s
buffer usage: 34 hits, 0 misses, 0 dirtied
WAL usage: 1 records, 0 full page images, 188 bytes
system usage: CPU: user: 0.01 s, system: 0.08 s, elapsed: 0.10 s
INFO: analyzing "public.test_db_bench_tenants"
INFO: "test_db_bench_tenants": scanned 30000 of 78154 pages, containing 1560000 live rows and 0 dead rows; 30000 rows in sample, 4064008 estimated total rows
VACUUM
# vacuum (analyze, verbose) test_db_bench_tenant_closure;
INFO: vacuuming "perfkit.public.test_db_bench_tenant_closure"
INFO: launched 1 parallel vacuum worker for index cleanup (planned: 1)
INFO: finished vacuuming "perfkit.public.test_db_bench_tenant_closure": index scans: 0
pages: 0 removed, 181573 remain, 3808 scanned (2.10% of total)
tuples: 0 removed, 28505125 remain, 0 are dead but not yet removable
removable cutoff: 27200205, which was 0 XIDs old when operation ended
index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed
avg read rate: 0.000 MB/s, avg write rate: 97.907 MB/s
buffer usage: 7680 hits, 0 misses, 3803 dirtied
WAL usage: 3800 records, 2 full page images, 224601 bytes
system usage: CPU: user: 0.08 s, system: 0.21 s, elapsed: 0.30 s
INFO: analyzing "public.test_db_bench_tenant_closure"
INFO: "test_db_bench_tenant_closure": scanned 30000 of 181573 pages, containing 4709835 live rows and 0 dead rows; 30000 rows in sample, 28505962 estimated total rows
VACUUM
Limit (cost=1.98..152.05 rows=1 width=44) (actual time=0.012..0.013 rows=0 loops=1)
-> Nested Loop (cost=1.98..1052.49 rows=7 width=44) (actual time=0.011..0.012 rows=0 loops=1)
-> Nested Loop (cost=1.55..1022.18 rows=7 width=37) (actual time=0.011..0.011 rows=0 loops=1)
-> Nested Loop (cost=1.12..1019.03 rows=7 width=8) (actual time=0.011..0.011 rows=0 loops=1)
-> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.56..2.77 rows=1 width=8) (actual time=0.010..0.010 rows=0 loops=1)
Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.56..1006.97 rows=929 width=16) (never executed)
Index Cond: (parent_id = tenants_parent.id)
Filter: (barrier <= 0)
-> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.43..0.45 rows=1 width=45) (never executed)
Index Cond: (id = tenants_closure.child_id)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_1_idx_tenant_id_3 on test_db_bench_1 (cost=0.43..2.98 rows=135 width=44) (never executed)
Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
Planning Time: 0.874 ms
Execution Time: 0.053 ms
(17 rows)
The planning time even increased :)
Played around with the indexes:
Firstly I dropped all the indexes that contained tenant_id field, except the one that is used in the execution plan:
DROP INDEX test_db_bench_1_idx_type_16;
DROP INDEX test_db_bench_1_idx_queue_18;
DROP INDEX test_db_bench_1_idx_queue_5;
After that:
Planning Time: 0.889 ms
Execution Time: 0.047 ms
DROP INDEX test_db_bench_1_idx_uuid_0;
Planning Time: 0.841 ms
Execution Time: 0.047 ms
DROP INDEX test_db_bench_1_idx_completion_time_ns_1;
DROP INDEX test_db_bench_1_idx_cti_entity_uuid_2;
DROP INDEX test_db_bench_1_idx_enqueue_time_ns_10;
Planning Time: 0.830 ms
Execution Time: 0.048 ms
DROP INDEX test_db_bench_1_idx_euc_id_4;
DROP INDEX test_db_bench_1_idx_policy_id_12;
DROP INDEX test_db_bench_1_idx_queue_19;
Planning Time: 0.826 ms
Execution Time: 0.044 ms
DROP INDEX test_db_bench_1_idx_queue_6;
DROP INDEX test_db_bench_1_idx_resource_id_11;
DROP INDEX test_db_bench_1_idx_resource_id_14;
Planning Time: 0.821 ms
Execution Time: 0.048 ms
DROP INDEX test_db_bench_1_idx_result_code_13;
DROP INDEX test_db_bench_1_idx_start_time_ns_9;
DROP INDEX test_db_bench_1_idx_state_8;
Planning Time: 0.803 ms
Execution Time: 0.044 ms
DROP INDEX test_db_bench_1_idx_type_15;
DROP INDEX test_db_bench_1_idx_type_17;
DROP INDEX test_db_bench_1_idx_update_time_ns_7;
At that moment only 3 indexes left on the table and a slight improvements in Planning Time:
Indexes:
"test_db_bench_1_pkey" PRIMARY KEY, btree (id)
"test_db_bench_1_idx_tenant_id_3" btree (tenant_id)
"test_db_bench_1_uuid_key" UNIQUE CONSTRAINT, btree (uuid)
Planning Time: 0.799 ms
Execution Time: 0.044 ms
I.e. the situation is still not good - almost all indexes have been removed, the planning time has been reduced insignificantly and it still remains much longer than the query execution time.
As for the stats - default_statistics_target has not been changed, has a value of 100, and no explicit settings for the columns have been applied ("Stats target" is empty).
Could it be a regression? I'll check it on PG14 when I get a chance.
--
Mikhail
On Mon, 11 Sept 2023 at 09:15, Laurenz Albe <laurenz.albe@cybertec.at> wrote:
On Fri, 2023-09-08 at 18:51 +0800, Mikhail Balayan wrote:
> I have three tables:
> - test_db_bench_1
> - test_db_bench_tenants
> - test_db_bench_tenant_closure
>
> And the query to join them:
> SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
> FROM "test_db_bench_1"
> JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
> AND ("tenants_child"."is_deleted" != true))
> JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
> AND ("tenants_closure"."barrier" <= 0))
> JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
> AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330'))
> AND ("tenants_parent"."is_deleted" != true))
> LIMIT 1
>
>
> With following execution plan:
>
> QUERY PLAN
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> ---------------
> Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1)
> -> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1)
> -> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1)
> -> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1)
> -> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1)
> Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed)
> Index Cond: (parent_id = tenants_parent.id)
> Filter: (barrier <= 0)
> -> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31 rows=1 width=44) (never executed)
> Index Cond: (id = tenants_closure.child_id)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44) (never executed)
> Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
> Planning Time: 0.732 ms
> Execution Time: 0.039 ms
>
>
> Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution.
>
> Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time.
You could try to VACUUM the involved tables; indexes with many entries pointing to dead tuples
can cause a long planing time.
Also, there are quite a lot of indexes on "test_db_bench_1". On a test database, drop some
indexes and see if that makes a difference.
Finally, check if "default_statistics_target" is set to a high value, or if the "Stats target"
for some column in the "\d+ tablename" output is set higher than 100.
Yours,
Laurenz Albe
Thanks for the idea. I was surprised to find that this is not the way it works and the planning time remains the same. To keep the experiment clean, I ran it several times, first a couple of times explain analyze, then a couple of times the query itself:
# PREPARE the_query (varchar) AS
SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
FROM "test_db_bench_1"
JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
AND ("tenants_child"."is_deleted" != true))
JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
AND ("tenants_closure"."barrier" <= 0))
JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
AND ("tenants_parent"."uuid" IN ($1))
AND ("tenants_parent"."is_deleted" != true))
LIMIT 1;
SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
FROM "test_db_bench_1"
JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
AND ("tenants_child"."is_deleted" != true))
JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
AND ("tenants_closure"."barrier" <= 0))
JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
AND ("tenants_parent"."uuid" IN ($1))
AND ("tenants_parent"."is_deleted" != true))
LIMIT 1;
# explain analyze EXECUTE the_query('4c79c1c5-21ae-45a0-8734-75d67abd0330');
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1.98..152.05 rows=1 width=152) (actual time=0.014..0.015 rows=0 loops=1)
-> Nested Loop (cost=1.98..1052.49 rows=7 width=152) (actual time=0.013..0.013 rows=0 loops=1)
-> Nested Loop (cost=1.55..1022.18 rows=7 width=108) (actual time=0.013..0.013 rows=0 loops=1)
-> Nested Loop (cost=1.12..1019.03 rows=7 width=63) (actual time=0.012..0.013 rows=0 loops=1)
-> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.56..2.77 rows=1 width=45) (actual time=0.012..0.012 rows=0 loops=1)
Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.56..1006.97 rows=929 width=18) (never executed)
Index Cond: (parent_id = tenants_parent.id)
Filter: (barrier <= 0)
-> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.43..0.45 rows=1 width=45) (never executed)
Index Cond: (id = tenants_closure.child_id)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_1_idx_tenant_id_3 on test_db_bench_1 (cost=0.43..2.98 rows=135 width=44) (never executed)
Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
Planning Time: 0.982 ms
Execution Time: 0.059 ms
(17 rows)
# explain analyze EXECUTE the_query('4c79c1c5-21ae-45a0-8734-75d67abd0330');
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1.98..152.05 rows=1 width=152) (actual time=0.011..0.012 rows=0 loops=1)
-> Nested Loop (cost=1.98..1052.49 rows=7 width=152) (actual time=0.010..0.011 rows=0 loops=1)
-> Nested Loop (cost=1.55..1022.18 rows=7 width=108) (actual time=0.010..0.011 rows=0 loops=1)
-> Nested Loop (cost=1.12..1019.03 rows=7 width=63) (actual time=0.010..0.010 rows=0 loops=1)
-> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.56..2.77 rows=1 width=45) (actual time=0.010..0.010 rows=0 loops=1)
Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.56..1006.97 rows=929 width=18) (never executed)
Index Cond: (parent_id = tenants_parent.id)
Filter: (barrier <= 0)
-> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.43..0.45 rows=1 width=45) (never executed)
Index Cond: (id = tenants_closure.child_id)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_1_idx_tenant_id_3 on test_db_bench_1 (cost=0.43..2.98 rows=135 width=44) (never executed)
Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
Planning Time: 0.843 ms
Execution Time: 0.046 ms
(17 rows)
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1.98..152.05 rows=1 width=152) (actual time=0.014..0.015 rows=0 loops=1)
-> Nested Loop (cost=1.98..1052.49 rows=7 width=152) (actual time=0.013..0.013 rows=0 loops=1)
-> Nested Loop (cost=1.55..1022.18 rows=7 width=108) (actual time=0.013..0.013 rows=0 loops=1)
-> Nested Loop (cost=1.12..1019.03 rows=7 width=63) (actual time=0.012..0.013 rows=0 loops=1)
-> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.56..2.77 rows=1 width=45) (actual time=0.012..0.012 rows=0 loops=1)
Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.56..1006.97 rows=929 width=18) (never executed)
Index Cond: (parent_id = tenants_parent.id)
Filter: (barrier <= 0)
-> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.43..0.45 rows=1 width=45) (never executed)
Index Cond: (id = tenants_closure.child_id)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_1_idx_tenant_id_3 on test_db_bench_1 (cost=0.43..2.98 rows=135 width=44) (never executed)
Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
Planning Time: 0.982 ms
Execution Time: 0.059 ms
(17 rows)
# explain analyze EXECUTE the_query('4c79c1c5-21ae-45a0-8734-75d67abd0330');
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit (cost=1.98..152.05 rows=1 width=152) (actual time=0.011..0.012 rows=0 loops=1)
-> Nested Loop (cost=1.98..1052.49 rows=7 width=152) (actual time=0.010..0.011 rows=0 loops=1)
-> Nested Loop (cost=1.55..1022.18 rows=7 width=108) (actual time=0.010..0.011 rows=0 loops=1)
-> Nested Loop (cost=1.12..1019.03 rows=7 width=63) (actual time=0.010..0.010 rows=0 loops=1)
-> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.56..2.77 rows=1 width=45) (actual time=0.010..0.010 rows=0 loops=1)
Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.56..1006.97 rows=929 width=18) (never executed)
Index Cond: (parent_id = tenants_parent.id)
Filter: (barrier <= 0)
-> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.43..0.45 rows=1 width=45) (never executed)
Index Cond: (id = tenants_closure.child_id)
Filter: (NOT is_deleted)
-> Index Scan using test_db_bench_1_idx_tenant_id_3 on test_db_bench_1 (cost=0.43..2.98 rows=135 width=44) (never executed)
Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
Planning Time: 0.843 ms
Execution Time: 0.046 ms
(17 rows)
# EXECUTE the_query('4c79c1c5-21ae-45a0-8734-75d67abd0330');
id | tenant_id
----+-----------
(0 rows)
Time: 1.311 ms
# EXECUTE the_query('4c79c1c5-21ae-45a0-8734-75d67abd0330');
id | tenant_id
----+-----------
(0 rows)
Time: 1.230 ms
id | tenant_id
----+-----------
(0 rows)
Time: 1.311 ms
# EXECUTE the_query('4c79c1c5-21ae-45a0-8734-75d67abd0330');
id | tenant_id
----+-----------
(0 rows)
Time: 1.230 ms
--
Mikhail
On Mon, 11 Sept 2023 at 09:23, Anupam b <abordia99@hotmail.com> wrote:
Also, if you write sql with bind params, planning time should be once for the sql. Subsequent sql will use cached stmt.From: Laurenz Albe <laurenz.albe@cybertec.at>
Sent: Sunday, September 10, 2023 6:15:43 PM
To: Mikhail Balayan <mv.balayan@gmail.com>; pgsql-performance@postgresql.org <pgsql-performance@postgresql.org>
Subject: Re: Planning time is time-consumingOn Fri, 2023-09-08 at 18:51 +0800, Mikhail Balayan wrote:
> I have three tables:
> - test_db_bench_1
> - test_db_bench_tenants
> - test_db_bench_tenant_closure
>
> And the query to join them:
> SELECT "test_db_bench_1"."id" id, "test_db_bench_1"."tenant_id"
> FROM "test_db_bench_1"
> JOIN "test_db_bench_tenants" AS "tenants_child" ON (("tenants_child"."uuid" = "test_db_bench_1"."tenant_id")
> AND ("tenants_child"."is_deleted" != true))
> JOIN "test_db_bench_tenant_closure" AS "tenants_closure" ON (("tenants_closure"."child_id" = "tenants_child"."id")
> AND ("tenants_closure"."barrier" <= 0))
> JOIN "test_db_bench_tenants" AS "tenants_parent" ON (("tenants_parent"."id" = "tenants_closure"."parent_id")
> AND ("tenants_parent"."uuid" IN ('4c79c1c5-21ae-45a0-8734-75d67abd0330'))
> AND ("tenants_parent"."is_deleted" != true))
> LIMIT 1
>
>
> With following execution plan:
>
> QUERY PLAN
> ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
> ---------------
> Limit (cost=1.56..1.92 rows=1 width=44) (actual time=0.010..0.011 rows=0 loops=1)
> -> Nested Loop (cost=1.56..162.42 rows=438 width=44) (actual time=0.009..0.009 rows=0 loops=1)
> -> Nested Loop (cost=1.13..50.27 rows=7 width=36) (actual time=0.008..0.009 rows=0 loops=1)
> -> Nested Loop (cost=0.84..48.09 rows=7 width=8) (actual time=0.008..0.009 rows=0 loops=1)
> -> Index Scan using test_db_bench_tenants_uuid on test_db_bench_tenants tenants_parent (cost=0.41..2.63 rows=1 width=8) (actual time=0.008..0.008 rows=0 loops=1)
> Index Cond: ((uuid)::text = '4c79c1c5-21ae-45a0-8734-75d67abd0330'::text)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_tenant_closure_pkey on test_db_bench_tenant_closure tenants_closure (cost=0.42..45.06 rows=40 width=16) (never executed)
> Index Cond: (parent_id = tenants_parent.id)
> Filter: (barrier <= 0)
> -> Index Scan using test_db_bench_tenants_pkey on test_db_bench_tenants tenants_child (cost=0.29..0.31 rows=1 width=44) (never executed)
> Index Cond: (id = tenants_closure.child_id)
> Filter: (NOT is_deleted)
> -> Index Scan using test_db_bench_1_idx_tenant_id_3 on acronis_db_bench_heavy (cost=0.43..14.66 rows=136 width=44) (never executed)
> Index Cond: ((tenant_id)::text = (tenants_child.uuid)::text)
> Planning Time: 0.732 ms
> Execution Time: 0.039 ms
>
>
> Where the planning time gets in the way as it takes an order of magnitude more time than the actual execution.
>
> Is there a possibility to reduce this time? And, in general, to understand why planning takes so much time.
You could try to VACUUM the involved tables; indexes with many entries pointing to dead tuples
can cause a long planing time.
Also, there are quite a lot of indexes on "test_db_bench_1". On a test database, drop some
indexes and see if that makes a difference.
Finally, check if "default_statistics_target" is set to a high value, or if the "Stats target"
for some column in the "\d+ tablename" output is set higher than 100.
Yours,
Laurenz Albe
On Mon, 2023-09-11 at 12:57 +0800, Mikhail Balayan wrote: > Thanks for the idea. I was surprised to find that this is not the way it works and the planning time remains the same. To benefit from the speed gains of a prepared statement, you'd have to execute it at least seven times. If a generic plan is used (which should happen), you will see $1 instead of the literal argument in the execution plan. Prepared statements are probably your best bet. Yours, Laurenz Albe
On Mon, 11 Sept 2023 at 18:16, Laurenz Albe <laurenz.albe@cybertec.at> wrote: > Also, there are quite a lot of indexes on "test_db_bench_1". On a test database, drop some > indexes and see if that makes a difference. Yeah, I count 3 that either have the key columns as some prefix of another index or are just a duplicate of some other index. Getting rid of those 3 will save some time in create_index_paths(). David
On Mon, 11 Sept 2023 at 21:54, Mikhail Balayan <mv.balayan@gmail.com> wrote: > Could it be a regression? I'll check it on PG14 when I get a chance. I'm not sure if you're asking for help here because you need planning to be faster than it currently is, or if it's because you believe that planning should always be faster than execution. If you think the latter, then you're mistaken. It seems to me that the complexity of planning this query is much more complex than executing it. The outer side of the inner-most nested loop finds 0 rows, so it need not scan the inner side, which results in that nested loop producing 0 rows, therefore the outer side of none of the subsequent nested loops find any rows. This is why you see "(never executed)" in the EXPLAIN ANALYZE. You could use perf record or perf top to dig into what's slow. On the other hand, please report back if you find PG14 to be much faster here. You could also experiment with a set of tables which are empty. It's possible getting the relation sizes are a factor to consider here. mdnblocks() needs to do a bit more work when the relation has multiple segments. David
Any statement that is executed has to go through the 4 stages of query execution:
- parse
- rewrite
- plan
- execute
The execute phase is the phase that mostly is the focus on, and is the phase in which normally is spent the most time.
In the postgres backend main loop, there are multiple ways of getting a statement to go through these stages.
The simple query execution is a single call that performs going through all these stages and the other common method is to use the client parse (which includes the server side parse and rewrite), bind (which performs the server side plan) and execute commands from this backend main loop.
A prepared statement, or named statement, is a way of performing statement execution where some of the intermediate results are stored in a memory area in the backend and thus allows the backend to persist some of the execution details. Non-prepared statement reuse the memory area, and thus flush any metadata.
The reason for explaining this is that when preparing a statement, the result of the phases of parse and rewrite, which is the parse tree, is stored.
That means that after the prepare, the work of generating the parse tree can be omitted by only performing calling bind and execute for the prepared/named statement.
The planner statistics are recorded for the calculated cost of a statement with the specified variables/binds, and record a cost of when the specified binds would be “non specific” alias generic.
After 5 times of execution of a prepared statement, if the generic plan is costed equal or lower during than the plan of the statement with the specified bind variables, then the backend will switch to the generic plan.
The advantage of switching to the generic plan is that it will not perform the plan costing and all accompanied transformations, but instead directly use the generic plan.
For this question, this would ’solve’ the issue of the plan phase taking more time than the execution, but potentially only after 5 times of executing the prepared statement.
The downside is that because the costing is skipped, it cannot choose another plan anymore for that named statement for the lifetime of the prepared statement in that backend, unless the backend is instructed explicitly to not to use the generic statement.
Frits Hoogland
On 11 Sep 2023, at 10:13, Laurenz Albe <laurenz.albe@cybertec.at> wrote:On Mon, 2023-09-11 at 12:57 +0800, Mikhail Balayan wrote:Thanks for the idea. I was surprised to find that this is not the way it works and the planning time remains the same.
To benefit from the speed gains of a prepared statement, you'd have to execute it
at least seven times. If a generic plan is used (which should happen), you will
see $1 instead of the literal argument in the execution plan.
Prepared statements are probably your best bet.
Yours,
Laurenz Albe
David Rowley <dgrowleyml@gmail.com> writes: > I'm not sure if you're asking for help here because you need planning > to be faster than it currently is, or if it's because you believe that > planning should always be faster than execution. If you think the > latter, then you're mistaken. Yeah. I don't see anything particularly troubling here. Taking circa three-quarters of a millisecond (on typical current hardware) to plan a four-way join on large tables is not unreasonable. In most cases one could expect the execution of such a query to take a good bit longer than that. I think the OP is putting too much emphasis on an edge case where execution finishes quickly because there are in fact zero rows matching the uuid restriction. BTW, in addition to the duplicative indexes, I wonder why the uuid columns being joined on aren't all of "uuid" type. While I doubt fixing that would move the needle greatly, it still seems sloppy. regards, tom lane
Hi Mikhail.
Postgresql version: 15.3 (Debian 15.3-1.pgdg110+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 10.2.1-6) 10.2.1 20210110, 64-bit
And just in case it matters, this is an experimental setup, so Postgresql running in Docker.
Are you using the official Docker Postgres image, specifically `postgres:15.3-bullseye`? ( https://hub.docker.com/_/postgres )
- If so, consider upgrading to version 15.4. It has some planner fixes not directly related to your issue. Check details here:
PostgreSQL 15.4 Release Notes https://www.postgresql.org/docs/release/15.4/
- For all technical text type id columns apply the `Collate "C"` . ( like `assign_time_str` and `cti_entity_uuid` )
PostgreSQL 15.4 Release Notes https://www.postgresql.org/docs/release/15.4/
- For all technical text type id columns apply the `Collate "C"` . ( like `assign_time_str` and `cti_entity_uuid` )
Alternatively, use the "uuid" column type everywhere, as Tom Lane suggests.
- Could you provide details on your current tuning settings? I'm interested in `work_mem`, `shared_buffers`, `effective_cache_size`, and others.
- Please test with different `work_mem` values.
If it's not too much trouble, can you also test with: ( These version uses a different locale and LLVM (JIT). )
- postgres:15.4-bookworm
- postgres:15.4-alpine3.18
Regards,
Imre
On Tue, 12 Sept 2023 at 02:27, Tom Lane <tgl@sss.pgh.pa.us> wrote: > > David Rowley <dgrowleyml@gmail.com> writes: > > I'm not sure if you're asking for help here because you need planning > > to be faster than it currently is, or if it's because you believe that > > planning should always be faster than execution. If you think the > > latter, then you're mistaken. > > Yeah. I don't see anything particularly troubling here. Taking > circa three-quarters of a millisecond (on typical current hardware) > to plan a four-way join on large tables is not unreasonable. I took a few minutes to reverse engineer the tables in question (with assistance from an AI bot) and ran the query in question. Unsurprisingly, I also see planning as slower than execution, but with a ratio of about planning being 12x slower than execution vs the reported ~18x. Planning Time: 0.581 ms Execution Time: 0.048 ms Nothing alarming in perf top of executing the query in pgbench with -M simple. I think this confirms the problem is just with expectations. 5.09% postgres [.] AllocSetAlloc 2.99% postgres [.] SearchCatCacheInternal 2.52% postgres [.] palloc 2.38% postgres [.] expression_tree_walker_impl 1.82% postgres [.] add_path_precheck 1.78% postgres [.] add_path 1.73% postgres [.] MemoryContextAllocZeroAligned 1.63% postgres [.] base_yyparse 1.61% postgres [.] CatalogCacheComputeHashValue 1.38% postgres [.] try_nestloop_path 1.36% postgres [.] stack_is_too_deep 1.33% postgres [.] add_paths_to_joinrel 1.19% postgres [.] core_yylex 1.18% postgres [.] lappend 1.15% postgres [.] initial_cost_nestloop 1.13% postgres [.] hash_search_with_hash_value 1.01% postgres [.] palloc0 0.95% postgres [.] get_memoize_path 0.90% postgres [.] equal 0.88% postgres [.] get_eclass_for_sort_expr 0.81% postgres [.] compare_pathkeys 0.80% postgres [.] bms_is_subset 0.77% postgres [.] ResourceArrayRemove 0.77% postgres [.] check_stack_depth 0.77% libc.so.6 [.] __memmove_avx_unaligned_erms 0.74% libc.so.6 [.] __memset_avx2_unaligned 0.73% postgres [.] AllocSetFree 0.71% postgres [.] final_cost_nestloop 0.69% postgres [.] compare_path_costs_fuzzily 0.68% postgres [.] initial_cost_mergejoin 0.64% libc.so.6 [.] __memset_avx2_unaligned_erms 0.61% postgres [.] create_nestloop_path 0.61% postgres [.] examine_variable 0.59% postgres [.] hash_bytes 0.56% postgres [.] truncate_useless_pathkeys 0.56% postgres [.] bms_overlap David
On Mon, Sep 11, 2023 at 11:07 PM David Rowley <dgrowleyml@gmail.com> wrote:
On Tue, 12 Sept 2023 at 02:27, Tom Lane <tgl@sss.pgh.pa.us> wrote:
>
> David Rowley <dgrowleyml@gmail.com> writes:
> > I'm not sure if you're asking for help here because you need planning
> > to be faster than it currently is, or if it's because you believe that
> > planning should always be faster than execution. If you think the
> > latter, then you're mistaken.
>
> Yeah. I don't see anything particularly troubling here. Taking
> circa three-quarters of a millisecond (on typical current hardware)
> to plan a four-way join on large tables is not unreasonable.
I took a few minutes to reverse engineer the tables in question (with
assistance from an AI bot) and ran the query in question.
Unsurprisingly, I also see planning as slower than execution, but with
a ratio of about planning being 12x slower than execution vs the
reported ~18x.
Planning Time: 0.581 ms
Execution Time: 0.048 ms
Nothing alarming in perf top of executing the query in pgbench with -M
simple. I think this confirms the problem is just with expectations.
Yep. Very fast executing queries often have faster execution than plan times. Postgres has a really dynamic version of SQL, for example, operator overloading for example, which probably doesn't help things. This is just the nature of SQL really. To improve things, just use prepared statements -- that's why they are there.
Aside, this style of SQL as produced for this test, guids, and record at a time thinking, is also not my cup of tea. There are some pros to it, but it tends to beat on a database. If you move this logic into the database, this kind of problem tends to evaporate. It's a very curious mode of thinking I see, that in order to "reduce load on the database", it is asked to set up and tear down a transaction for every single record fetched :).
merlin
SnipOn 15 Dec 2023, at 22:49, Merlin Moncure <mmoncure@gmail.com> wrote:On Mon, Sep 11, 2023 at 11:07 PM David Rowley <dgrowleyml@gmail.com> wrote:
I took a few minutes to reverse engineer the tables in question (with
assistance from an AI bot) and ran the query in question.
Unsurprisingly, I also see planning as slower than execution, but with
a ratio of about planning being 12x slower than execution vs the
reported ~18x.
Planning Time: 0.581 ms
Execution Time: 0.048 ms
Nothing alarming in perf top of executing the query in pgbench with -M
simple. I think this confirms the problem is just with expectations.Yep. Very fast executing queries often have faster execution than plan times. Postgres has a really dynamic version of SQL, for example, operator overloading for example, which probably doesn't help things. This is just the nature of SQL really. To improve things, just use prepared statements -- that's why they are there.
Just to add my 2 cents: use prepared statements and - when applicable force generic plans: https://www.postgresql.org/docs/current/runtime-config-query.html#GUC-PLAN-CACHE-MODE
—
Michal