Skip to content

Commit 4910c04

Browse files
Atsushi TorikoshiCommitfest Bot
Atsushi Torikoshi
authored and
Commitfest Bot
committed
Add function to log the plan of the currently running query
Currently, we have to wait for the query execution to finish to know its plan either using EXPLAIN ANALYZE or auto_explain. This is not so convenient, for example when investigating long-running queries on production environments. To improve this situation, this adds pg_log_query_plan() function that requests to log the plan of the currently executing query. On receipt of the request, codes for logging plan is wrapped to every ExecProcNode and when the executor runs one of ExecProcNode, the plan is actually logged. These wrappers are unwrapped when once the plan is logged. In this way, we can avoid adding the overhead which we'll face when adding CHECK_FOR_INTERRUPTS() like mechanisms in somewhere in executor codes safely. By default, only superusers are allowed to request to log the plans because allowing any users to issue this request at an unbounded rate would cause lots of log messages and which can lead to denial of service.
1 parent 0d4dad2 commit 4910c04

File tree

22 files changed

+613
-43
lines changed

22 files changed

+613
-43
lines changed

‎contrib/auto_explain/auto_explain.c

+4-20
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include <limits.h>
1616

1717
#include "access/parallel.h"
18+
#include "commands/dynamic_explain.h"
1819
#include "commands/explain.h"
1920
#include "commands/explain_format.h"
2021
#include "commands/explain_state.h"
@@ -412,26 +413,9 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
412413
es->format = auto_explain_log_format;
413414
es->settings = auto_explain_log_settings;
414415

415-
ExplainBeginOutput(es);
416-
ExplainQueryText(es, queryDesc);
417-
ExplainQueryParameters(es, queryDesc->params, auto_explain_log_parameter_max_length);
418-
ExplainPrintPlan(es, queryDesc);
419-
if (es->analyze && auto_explain_log_triggers)
420-
ExplainPrintTriggers(es, queryDesc);
421-
if (es->costs)
422-
ExplainPrintJITSummary(es, queryDesc);
423-
ExplainEndOutput(es);
424-
425-
/* Remove last line break */
426-
if (es->str->len > 0 && es->str->data[es->str->len - 1] == '\n')
427-
es->str->data[--es->str->len] = '\0';
428-
429-
/* Fix JSON to output an object */
430-
if (auto_explain_log_format == EXPLAIN_FORMAT_JSON)
431-
{
432-
es->str->data[0] = '{';
433-
es->str->data[es->str->len - 1] = '}';
434-
}
416+
ExplainStringAssemble(es, queryDesc, auto_explain_log_format,
417+
auto_explain_log_triggers,
418+
auto_explain_log_parameter_max_length);
435419

436420
/*
437421
* Note: we rely on the existing logging of context or

‎doc/src/sgml/func.sgml

+55
Original file line numberDiff line numberDiff line change
@@ -28821,6 +28821,29 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres}
2882128821
</para></entry>
2882228822
</row>
2882328823

28824+
<row>
28825+
<entry role="func_table_entry"><para role="func_signature">
28826+
<indexterm>
28827+
<primary>pg_log_query_plan</primary>
28828+
</indexterm>
28829+
<function>pg_log_query_plan</function> ( <parameter>pid</parameter> <type>integer</type> )
28830+
<returnvalue>boolean</returnvalue>
28831+
</para>
28832+
<para>
28833+
Requests to log the plan of the query currently running on the
28834+
backend with specified process ID.
28835+
It will be logged at <literal>LOG</literal> message level and
28836+
will appear in the server log based on the log
28837+
configuration set (See <xref linkend="runtime-config-logging"/>
28838+
for more information), but will not be sent to the client
28839+
regardless of <xref linkend="guc-client-min-messages"/>.
28840+
</para>
28841+
<para>
28842+
This function is restricted to superusers by default, but other
28843+
users can be granted EXECUTE to run the function.
28844+
</para></entry>
28845+
</row>
28846+
2882428847
<row>
2882528848
<entry role="func_table_entry"><para role="func_signature">
2882628849
<indexterm>
@@ -28971,6 +28994,38 @@ stats_timestamp | 2025-03-24 13:55:47.796698+01
2897128994
will be less resource intensive when only the local backend is of interest.
2897228995
</para>
2897328996
</note>
28997+
</para>
28998+
28999+
<para>
29000+
<function>pg_log_query_plan</function> can be used
29001+
to log the plan of a backend process. For example:
29002+
<programlisting>
29003+
postgres=# SELECT pg_log_query_plan(201116);
29004+
pg_log_query_plan
29005+
---------------------------
29006+
t
29007+
(1 row)
29008+
</programlisting>
29009+
The format of the query plan is the same as when <literal>VERBOSE</literal>,
29010+
<literal>COSTS</literal>, <literal>SETTINGS</literal> and
29011+
<literal>FORMAT TEXT</literal> are used in the <command>EXPLAIN</command>
29012+
command. For example:
29013+
<screen>
29014+
LOG: query plan running on backend with PID 201116 is:
29015+
Query Text: SELECT * FROM pgbench_accounts;
29016+
Seq Scan on public.pgbench_accounts (cost=0.00..52787.00 rows=2000000 width=97)
29017+
Output: aid, bid, abalance, filler
29018+
Settings: work_mem = '1MB'
29019+
Query Identifier: 8621255546560739680
29020+
</screen>
29021+
Note that when the target is executing nested statements(statements executed
29022+
inside a function), only the innermost query plan is logged.
29023+
Note that logging plan may take some time, as it occurs when
29024+
the plan node is executed. For example, when a query is
29025+
running <function>pg_sleep</function>, the plan will not be
29026+
logged until the function execution completes.
29027+
Similarly, when a query is running under the extended query
29028+
protocol, the plan is logged only during the execute step.
2897429029
</para>
2897529030

2897629031
</sect2>

‎src/backend/access/transam/xact.c

+7
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "catalog/pg_enum.h"
3737
#include "catalog/storage.h"
3838
#include "commands/async.h"
39+
#include "commands/dynamic_explain.h"
3940
#include "commands/tablecmds.h"
4041
#include "commands/trigger.h"
4142
#include "common/pg_prng.h"
@@ -2904,6 +2905,9 @@ AbortTransaction(void)
29042905
/* Reset snapshot export state. */
29052906
SnapBuildResetExportedSnapshotState();
29062907

2908+
/* Reset pg_log_query_plan() related state. */
2909+
ResetLogQueryPlanState();
2910+
29072911
/*
29082912
* If this xact has started any unfinished parallel operation, clean up
29092913
* its workers and exit parallel mode. Don't warn about resources.
@@ -5296,6 +5300,9 @@ AbortSubTransaction(void)
52965300
/* Reset logical state. */
52975301
ResetLogicalState();
52985302

5303+
/* Reset pg_log_query_plan() related state. */
5304+
ResetLogQueryPlanState();
5305+
52995306
/*
53005307
* No need for SnapBuildResetExportedSnapshotState() here, snapshot
53015308
* exports are not supported in subtransactions.

‎src/backend/catalog/system_functions.sql

+2
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,8 @@ REVOKE EXECUTE ON FUNCTION pg_ls_logicalmapdir() FROM PUBLIC;
775775

776776
REVOKE EXECUTE ON FUNCTION pg_ls_replslotdir(text) FROM PUBLIC;
777777

778+
REVOKE EXECUTE ON FUNCTION pg_log_query_plan(integer) FROM PUBLIC;
779+
778780
--
779781
-- We also set up some things as accessible to standard roles.
780782
--

‎src/backend/commands/Makefile

+1
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ OBJS = \
3232
define.o \
3333
discard.o \
3434
dropcmds.o \
35+
dynamic_explain.o \
3536
event_trigger.o \
3637
explain.o \
3738
explain_dr.o \

0 commit comments

Comments
 (0)