Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gXlqn-0007Gb-Pf for pgsql-hackers@arkaria.postgresql.org; Fri, 14 Dec 2018 11:41:50 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.89) (envelope-from ) id 1gXlqk-0005Fu-Sk for pgsql-hackers@arkaria.postgresql.org; Fri, 14 Dec 2018 11:41:46 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gXlqk-00059q-8V for pgsql-hackers@lists.postgresql.org; Fri, 14 Dec 2018 11:41:46 +0000 Received: from mail-wr1-x434.google.com ([2a00:1450:4864:20::434]) by makus.postgresql.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_CBC_SHA1:256) (Exim 4.89) (envelope-from ) id 1gXlqg-0000iv-7I for pgsql-hackers@lists.postgresql.org; Fri, 14 Dec 2018 11:41:45 +0000 Received: by mail-wr1-x434.google.com with SMTP id j2so5147571wrw.1 for ; Fri, 14 Dec 2018 03:41:41 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=2ndquadrant-com.20150623.gappssmtp.com; s=20150623; h=to:from:subject:message-id:date:user-agent:mime-version :content-language; bh=3jD5cnIp0G17bCpA2mVcO9VLMmlwyqZRDHY9GsCVots=; b=abKpqJFQIQSZcB16FnXjEvnogcq8DClOT0zNcRi/NRxGM+jr5kJCx7e7XEnf1h5Qq7 5NzCaCghAsTuTXqdWCf1pAc2bLsKlQOfwwjGD2PH4+txJ8pTQmJYGVS6/ZIOGbkazF2X ht3rV4+diIL1RYebWjyDsZ8N0NWJrx7mDmGsvECVm62vlTL3mcqZCnEfgGI19wFM/kha rvuFbYKZXOuGR0K4ZfmltzaRxdFekVG9m6UxP0Jkn2SB5cFsMCm/LakaxgdKmnb/3LCP CayKdoQXeu/LKxkCItuuQn1vkUr5QecUtxXWyrOoaAfQ++2QCro4JhAvMSi7Nmq+0lEe zOTg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:to:from:subject:message-id:date:user-agent :mime-version:content-language; bh=3jD5cnIp0G17bCpA2mVcO9VLMmlwyqZRDHY9GsCVots=; b=SglLtNRPVgwopk5rYYgPK1B/+I3SG0SvisJV+61xl6y0QhRRrrawxEf6oxfxEvGeYM V7O7RjergA9YOBHEF2aIwj8+Qce+wxpBNp7D6432zCXhcyfVF80RHbtzoZIUC0bVwWed 32aPwI2crlssFd1hMMD7/FjEdFZ0bJZiTniX3pWNbwpbfTFgCZ5VZsnjZFFcMk6TSaFX Waw5paVjniRQNECTeBDEZEtoctMueJh6uMoc/1SE3J9DLkZ7QJa5XyTcMofK7iwb6Jez q3BSupbyVWffHVaijEOO8uCEMDCvDzSv2Er+y6DmodZ8LIeJO0MIJDuiwWzNNt2qMHB8 dQfg== X-Gm-Message-State: AA+aEWYB94lRD/VbckSofIp3hpuW/VwtSuoJY7yqkCiIQuajrlo2UGcF yaIL4X9irDkNXuJnXVrnpgJnHWweg53BiUE9iRHpzyguqSkwoDGdYvS53bx+5Ysyq8PVp5UVZqF /FCb3UxHjKFMMr4o/pNhR4f4kJ9XjpgPm458+NsCpneGBwi6/0avtTvd6z8CX9lnkpH+xEXuYyi WDEFGQUyTDabwdVlkXAZUpI3vd X-Google-Smtp-Source: AFSGD/VfHlOkuDKH4fv1XUC0ylhNDPVz6pLKn+3qkBsKPhw3h14pKThsGWmkva5JZizec9zQOVy0WA== X-Received: by 2002:adf:ef50:: with SMTP id c16mr2328893wrp.198.1544787699394; Fri, 14 Dec 2018 03:41:39 -0800 (PST) Received: from [10.137.2.21] (static-84-42-175-93.net.upcbroadband.cz. [84.42.175.93]) by smtp.gmail.com with ESMTPSA id r64sm5456167wmg.5.2018.12.14.03.41.38 for (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Fri, 14 Dec 2018 03:41:38 -0800 (PST) To: PostgreSQL Hackers From: Tomas Vondra Subject: explain plans with information about (modified) gucs Message-ID: Date: Fri, 14 Dec 2018 12:41:36 +0100 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Thunderbird/60.3.1 MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="------------C337278E9E189823489B72F9" Content-Language: en-US List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk This is a multi-part message in MIME format. --------------C337278E9E189823489B72F9 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 7bit Hi, every now and then I have to investigate an execution plan that is strange in some way and I can't reproduce the same behavior. Usually it's simply due to data distribution changing since the problem was observed (say, after a nightly batch load/update). In many cases it however may be due to some local GUC tweaks, usually addressing some query specific issues (say, disabling nested loops or lowering join_collapse_limit). I've repeatedly ran into cases where the GUC was not properly reset to the "regular" value, and it's rather difficult to identify this is what's happening. Or cases with different per-user settings and connection pooling (SET SESSION AUTHORIZATION / ROLE etc.). So I propose to extend EXPLAIN output with an additional option, which would include information about modified GUCs in the execution plan (disabled by default, of course): test=# explain (gucs) select * from t; QUERY PLAN -------------------------------------------------------------------- Seq Scan on t (cost=0.00..35.50 rows=2550 width=4) GUCs: application_name = 'x', client_encoding = 'UTF8', cpu_tuple_cost = '0.01' (2 rows) Of course, this directly applies to auto_explain too, which gets a new option log_gucs. The patch is quite trivial, but there are about three open questions: 1) names of the options I'm not particularly happy with calling the option "gucs" - it's an acronym and many users have little idea what GUC stands for. So I think a better name would be desirable, but I'm not sure what would that be. Options? Parameters? 2) format of output At this point the names/values are simply formatted into a one-line string. That's not particularly readable, and it's not very useful for the YAML/JSON formats I guess. So adding each modified GUC as an extra text property would be better. 3) identifying modified (and interesting) GUCs We certainly don't want to include all GUCs, so the question is how to decide which GUCs are interesting. The simplest approach would be to look for GUCs that changed in the session (source == PGC_S_SESSION), but that does not help with SET SESSION AUTHORIZATION / ROLE cases. So we probably want (source > PGC_S_ARGV), but probably not PGC_S_OVERRIDE because that includes irrelevant options like wal_buffers etc. For now I've used /* return only options that were modified (not as in config file) */ if ((conf->source <= PGC_S_ARGV) || (conf->source == PGC_S_OVERRIDE)) continue; which generally does the right thing, although it also includes stuff like application_name or client_encoding. But perhaps it'd be better to whitelist the GUCs in some way, because some of the user-defined GUCs may be sensitive and should not be included in plans. Opinions? regards -- Tomas Vondra http://www.2ndQuadrant.com PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services --------------C337278E9E189823489B72F9 Content-Type: text/x-patch; name="explain-with-gucs.diff" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="explain-with-gucs.diff" diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c index 646cd0d42c..ec44c59b7f 100644 --- a/contrib/auto_explain/auto_explain.c +++ b/contrib/auto_explain/auto_explain.c @@ -28,6 +28,7 @@ static bool auto_explain_log_verbose = false; static bool auto_explain_log_buffers = false; static bool auto_explain_log_triggers = false; static bool auto_explain_log_timing = true; +static bool auto_explain_log_gucs = false; static int auto_explain_log_format = EXPLAIN_FORMAT_TEXT; static int auto_explain_log_level = LOG; static bool auto_explain_log_nested_statements = false; @@ -112,6 +113,17 @@ _PG_init(void) NULL, NULL); + DefineCustomBoolVariable("auto_explain.log_gucs", + "Print modified GUC values.", + NULL, + &auto_explain_log_gucs, + false, + PGC_SUSET, + 0, + NULL, + NULL, + NULL); + DefineCustomBoolVariable("auto_explain.log_verbose", "Use EXPLAIN VERBOSE for plan logging.", NULL, @@ -356,6 +368,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc) es->timing = (es->analyze && auto_explain_log_timing); es->summary = es->analyze; es->format = auto_explain_log_format; + es->gucs = auto_explain_log_gucs; ExplainBeginOutput(es); ExplainQueryText(es, queryDesc); diff --git a/doc/src/sgml/auto-explain.sgml b/doc/src/sgml/auto-explain.sgml index 120b168d45..852c69b7bb 100644 --- a/doc/src/sgml/auto-explain.sgml +++ b/doc/src/sgml/auto-explain.sgml @@ -169,6 +169,23 @@ LOAD 'auto_explain'; + + + auto_explain.log_gucs (boolean) + + auto_explain.log_gucs configuration parameter + + + + + auto_explain.log_gucs controls whether information + about modified configuration options are logged with the execution + plan. Only options modified at the database, user, client or session + level are considered modified. This parameter is off by default. + + + + auto_explain.log_format (enum) diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index de09ded65b..b8cab69f71 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -31,6 +31,7 @@ #include "storage/bufmgr.h" #include "tcop/tcopprot.h" #include "utils/builtins.h" +#include "utils/guc_tables.h" #include "utils/json.h" #include "utils/lsyscache.h" #include "utils/rel.h" @@ -164,6 +165,8 @@ ExplainQuery(ParseState *pstate, ExplainStmt *stmt, const char *queryString, es->costs = defGetBoolean(opt); else if (strcmp(opt->defname, "buffers") == 0) es->buffers = defGetBoolean(opt); + else if (strcmp(opt->defname, "gucs") == 0) + es->gucs = defGetBoolean(opt); else if (strcmp(opt->defname, "timing") == 0) { timing_set = true; @@ -547,6 +550,37 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, /* Create textual dump of plan tree */ ExplainPrintPlan(es, queryDesc); + if (es->gucs) + { + int i; + int num; + StringInfoData str; + struct config_generic **gucs; + + gucs = get_modified_guc_options(&num); + + for (i = 0; i < num; i++) + { + char *setting; + struct config_generic *conf = gucs[i]; + + if (i == 0) + initStringInfo(&str); + else + appendStringInfoString(&str, ", "); + + setting = GetConfigOptionByName(conf->name, NULL, true); + + if (setting) + appendStringInfo(&str, "%s = '%s'", conf->name, setting); + else + appendStringInfo(&str, "%s = NULL", conf->name); + } + + if (num > 0) + ExplainPropertyText("GUCs", str.data, es); + } + if (es->summary && planduration) { double plantime = INSTR_TIME_GET_DOUBLE(*planduration); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 6fe1939881..fd4473d9d0 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -8556,6 +8556,37 @@ ShowAllGUCConfig(DestReceiver *dest) end_tup_output(tstate); } +struct config_generic ** +get_modified_guc_options(int *num) +{ + int i; + struct config_generic **result; + + *num = 0; + result = palloc(sizeof(struct config_generic *) * num_guc_variables); + + for (i = 0; i < num_guc_variables; i++) + { + struct config_generic *conf = guc_variables[i]; + + /* return only options visible to the user */ + if ((conf->flags & GUC_NO_SHOW_ALL) || + ((conf->flags & GUC_SUPERUSER_ONLY) && + !is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_SETTINGS))) + continue; + + /* return only options that were modified (not as in config file) */ + if ((conf->source <= PGC_S_ARGV) || (conf->source == PGC_S_OVERRIDE)) + continue; + + /* assign to the values array */ + result[*num] = conf; + *num = *num + 1; + } + + return result; +} + /* * Return GUC variable value by name; optionally return canonical form of * name. If the GUC is unset, then throw an error unless missing_ok is true, diff --git a/src/include/commands/explain.h b/src/include/commands/explain.h index d3f70fda08..05afca22aa 100644 --- a/src/include/commands/explain.h +++ b/src/include/commands/explain.h @@ -35,6 +35,7 @@ typedef struct ExplainState bool buffers; /* print buffer usage */ bool timing; /* print detailed node timing */ bool summary; /* print total planning and execution timing */ + bool gucs; /* print modified GUCs */ ExplainFormat format; /* output format */ /* state for output formatting --- not reset for each new plan tree */ int indent; /* current indentation level */ diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h index 6f9fdb6a5f..4942e192d6 100644 --- a/src/include/utils/guc_tables.h +++ b/src/include/utils/guc_tables.h @@ -267,5 +267,6 @@ extern void build_guc_variables(void); extern const char *config_enum_lookup_by_value(struct config_enum *record, int val); extern bool config_enum_lookup_by_name(struct config_enum *record, const char *value, int *retval); +extern struct config_generic **get_modified_guc_options(int *num); #endif /* GUC_TABLES_H */ --------------C337278E9E189823489B72F9 Content-Type: application/pgp-keys; name="pEpkey.asc" Content-Transfer-Encoding: 7bit Content-Disposition: attachment; filename="pEpkey.asc" -----BEGIN PGP PUBLIC KEY BLOCK----- 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 =hJFM -----END PGP PUBLIC KEY BLOCK----- --------------C337278E9E189823489B72F9--