Profiling Long Queries
This feature allows you to track the query execution status without waiting for it to finish. Note that enabling profiling brings a certain amount of cost for all backend queries.
Preparation
To enable profiling, you must set query_profiling parameter in the settings
(either in the configuration file, or dynamically using SET command).
To change the size of the internal plan cache queue, there is a parameter query_profiling_queue_size. The minimum size is 8MB.
Setting example:
query_profiling_queue_size = 32MB
Profiling also requires a functioning message queue, in particular,
SELECT start_mq_broker(); call. Then, all interaction occurs with system queues
that are already created.
Information analysis is very sensitive to static information supported by the
DBMS, Therefore, it is recommended to use the ANALYZE command for the tables
to be examined.
Information Analysis
The module provides 3 main functions for obtaining information about the query execution:
- qhb_running_queries
- qhb_dump_qep
- qhb_wait_qep
qhb_running_queries returns a temporary table that contains the following information:
| Column Name | Type | Description | Comment |
|---|---|---|---|
| query_id | int8 | Unique query identifier | |
| backend_pid | int8 | PID of the postgres backend on which the query is executed | The same as the value from pg_stat_activity.pid |
| query_string | text | Query text string | |
| query_started | timestamptz | Query start time | |
| ETC | timestamptz | Estimated completion time | May be NULL, with complex queries the estimate may be far from reality |
| elapsed_counter | float8 | Internal counter to track completed query work | |
| estimated_total_counter | float8 | Internal expected amount of query work |
qhb_dump_qep takes the query ID from qhb_running_queries and displays the query plan tree. The second argument is a string with the formatting method:
- 'full' displays complete counter values;
- 'scaled' displays counter values resolved to the one order (displayed in the units field);
- 'scientific' displays counter values;
- 'percent' displays counter values in percentage;
- 'mixed_percent' displays counter values in percentage and absolute expected value;
The function returns a plan tree that is the same as the one returned by EXPLAIN, as well as the execution status for each node. Note that nodes can have progress of 100%, but still be called again, increasing the cycle counter. Example of query analysis:
# select start_mq_broker();
start_mq_broker
-----------------
(1 row)
# create table tb (x int);
CREATE TABLE
# insert into tb select generate_series(1, 50000);
INSERT 0 50000
# select a.x, count(*) from tb a, tb b group by a.x;
In another client:
# select * from qhb_running_queries();
query_id | backend_pid | query_string | query_started | ETC | elapsed_counter | estimated_total_counter
----------+-------------+----------------------------------------------------+-------------------------------+------------------------------+------------------+-------------------------
6 | 2475 | select a.x, count(*) from tb a, tb b group by a.x; | 2025-07-09 18:48:23.397635+03 | 2025-07-18 15:26:46.20484+03 | 4846879.43268446 | 99537037.55000001
(1 row)
# select qhb_dump_qep(query_id, 'percent') from qhb_running_queries() where query_string like 'select a.x%';
qhb_dump_qep
---------------------------------------------------------------
(0,"~ Agg",N/A," (progress: 0.00%, loops: 0)",0)
(1,"~ Nest Loop",N/A," (progress: 5.28%, loops: 0)",0)
(2,"~ Seq Scan"," on tb"," (progress: 5.98%, loops: 0)",1)
(3,"-> Material",N/A," (progress: 88.32%, loops: 3384)",1)
(4,"~ Seq Scan"," on tb"," (progress: 88.32%, loops: 0)",3)
(5 rows)
The qhb_wait_qep function has similar logic, but is designed to wait for a query that was executed earlier. The function takes the query ID obtained from the qhb_sql_plans_info view, plan cache. In addition, the second argument must specify the PID of the backend on which the query should start to be executed. The third argument, similar to qhb_dump_qep, is a string with the formatting method. The query timeout is limited to 30 seconds.
Example:
# select queryid, query from qhb_sql_plans_info;
queryid | query
----------------------+------------------------------------------------------
5039774379276303448 | select a.x, count(*) from tb a, tb b group by a.x;
# select qhb_wait_qep(5039774379276303448, 1932, 'scaled');
qhb_wait_qep
----------------------------------------------------------------------------
("~ Agg",N/A," (progress: 0.00 / 56083831.48, loops: 0, units: 0)")
("-> Nest Loop",N/A," (progress: 2.62 / 400.60, loops: 0, units: 5)")
("~ Seq Scan"," on tb"," (progress: 5.85 / 788.10, loops: 0, units: 0)")
("~ Material",N/A," (progress: 3.96 / 4.50, loops: 419, units: 5)")
("~ Seq Scan"," on tb"," (progress: 6.96 / 7.88, loops: 0, units: 2)")
Limitations
The implementation in its current form has limitations:
- The ETC value is predicted by interpolation from just two points in time, which means that this value can vary widely over time.
- To analyze the query work, cost estimates of the plan tree nodes are used, received from the scheduler. These values may not be accurate and are not adjusted during the query execution.