From cfc72be01d93b356a47c1e75a2fed21795d2a1a5 Mon Sep 17 00:00:00 2001 From: niksa Date: Thu, 26 Sep 2019 23:05:21 +0200 Subject: [PATCH] Show explain from data nodes We want to get more insights about what plans are executed on data nodes. If a user runs explain with verbose option we will connect to each data node, run explain on data node and print output together with existing explain output. --- src/guc.c | 12 ++ src/guc.h | 1 + tsl/src/fdw/data_node_scan_exec.c | 5 +- tsl/src/fdw/fdw.c | 4 +- tsl/src/fdw/scan_exec.c | 120 ++++++++++--- tsl/src/fdw/scan_exec.h | 3 +- .../expected/hypertable_distributed-11.out | 169 ++++++++++++++++++ .../expected/hypertable_distributed-12.out | 169 ++++++++++++++++++ tsl/test/sql/hypertable_distributed.sql.in | 18 ++ 9 files changed, 469 insertions(+), 32 deletions(-) diff --git a/src/guc.c b/src/guc.c index 2c93df964..12a4a3079 100644 --- a/src/guc.c +++ b/src/guc.c @@ -61,6 +61,7 @@ TSDLLEXPORT bool ts_guc_enable_connection_binary_data; TSDLLEXPORT bool ts_guc_enable_client_ddl_on_data_nodes = false; TSDLLEXPORT char *ts_guc_ssl_dir = NULL; TSDLLEXPORT char *ts_guc_passfile = NULL; +TSDLLEXPORT bool ts_guc_enable_remote_explain = false; #ifdef TS_DEBUG bool ts_shutdown_bgw = false; @@ -274,6 +275,17 @@ _guc_init(void) NULL, NULL); + DefineCustomBoolVariable("timescaledb.enable_remote_explain", + "Show explain from remote nodes when using VERBOSE flag", + "Enable getting and showing EXPLAIN output from remote nodes", + &ts_guc_enable_remote_explain, + false, + PGC_USERSET, + 0, + NULL, + NULL, + NULL); + DefineCustomStringVariable("timescaledb.ssl_dir", "TimescaleDB user certificate directory", "Determines a path which is used to search user certificates and " diff --git a/src/guc.h b/src/guc.h index 05a2f84fb..dd4bc967e 100644 --- a/src/guc.h +++ b/src/guc.h @@ -37,6 +37,7 @@ extern TSDLLEXPORT bool ts_guc_enable_connection_binary_data; extern TSDLLEXPORT bool ts_guc_enable_client_ddl_on_data_nodes; extern TSDLLEXPORT char *ts_guc_ssl_dir; extern TSDLLEXPORT char *ts_guc_passfile; +extern TSDLLEXPORT bool ts_guc_enable_remote_explain; #ifdef TS_DEBUG extern bool ts_shutdown_bgw; diff --git a/tsl/src/fdw/data_node_scan_exec.c b/tsl/src/fdw/data_node_scan_exec.c index 86e8dfe88..39aae5cc1 100644 --- a/tsl/src/fdw/data_node_scan_exec.c +++ b/tsl/src/fdw/data_node_scan_exec.c @@ -23,6 +23,7 @@ #include "data_node_scan_exec.h" #include "async_append.h" #include "remote/cursor.h" +#include "guc.h" /* * The execution stage of a DataNodeScan. @@ -50,7 +51,7 @@ data_node_scan_begin(CustomScanState *node, EState *estate, int eflags) List *recheck_quals = lsecond(cscan->custom_exprs); List *fdw_private = list_nth(cscan->custom_private, 0); - if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + if ((eflags & EXEC_FLAG_EXPLAIN_ONLY) && !ts_guc_enable_remote_explain) return; fdw_scan_init(&node->ss, &sss->fsstate, cscan->custom_relids, fdw_private, fdw_exprs, eflags); @@ -138,7 +139,7 @@ data_node_scan_explain(CustomScanState *node, List *ancestors, ExplainState *es) CustomScan *scan = (CustomScan *) node->ss.ps.plan; List *fdw_private = list_nth(scan->custom_private, 0); - fdw_scan_explain(&node->ss, fdw_private, es); + fdw_scan_explain(&node->ss, fdw_private, es, &((DataNodeScanState *) node)->fsstate); } static CustomExecMethods data_node_scan_state_methods = { diff --git a/tsl/src/fdw/fdw.c b/tsl/src/fdw/fdw.c index 3ff56be5d..8a32efcb4 100644 --- a/tsl/src/fdw/fdw.c +++ b/tsl/src/fdw/fdw.c @@ -156,7 +156,7 @@ begin_foreign_scan(ForeignScanState *node, int eflags) { ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan; - if (eflags & EXEC_FLAG_EXPLAIN_ONLY) + if ((eflags & EXEC_FLAG_EXPLAIN_ONLY) && !ts_guc_enable_remote_explain) return; node->fdw_state = (TsFdwScanState *) palloc0(sizeof(TsFdwScanState)); @@ -275,7 +275,7 @@ explain_foreign_scan(ForeignScanState *node, struct ExplainState *es) { List *fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private; - fdw_scan_explain(&node->ss, fdw_private, es); + fdw_scan_explain(&node->ss, fdw_private, es, (TsFdwScanState *) node->fdw_state); } static void diff --git a/tsl/src/fdw/scan_exec.c b/tsl/src/fdw/scan_exec.c index 9e777f301..7e01bf1e5 100644 --- a/tsl/src/fdw/scan_exec.c +++ b/tsl/src/fdw/scan_exec.c @@ -20,6 +20,7 @@ #include "scan_exec.h" #include "utils.h" #include "remote/cursor.h" +#include "guc.h" /* * Indexes of FDW-private information stored in fdw_private lists. @@ -179,26 +180,14 @@ prepare_query_params(PlanState *node, List *fdw_exprs, int num_params, FmgrInfo *param_values = (const char **) palloc0(num_params * sizeof(char *)); } -void -fdw_scan_init(ScanState *ss, TsFdwScanState *fsstate, Bitmapset *scanrelids, List *fdw_private, - List *fdw_exprs, int eflags) +static TSConnection * +get_connection(ScanState *ss, Oid const server_id, Bitmapset *scanrelids, List *exprs) { Scan *scan = (Scan *) ss->ps.plan; EState *estate = ss->ps.state; RangeTblEntry *rte; TSConnectionId id; int rtindex; - int num_params; - - /* - * Do nothing in EXPLAIN (no ANALYZE) case. fdw_state stays NULL. - */ - if (eflags & EXEC_FLAG_EXPLAIN_ONLY) - return; - - /* - * We'll save private state in node->fdw_state. - */ /* * Identify which user to do the remote access as. This should match what @@ -213,19 +202,30 @@ fdw_scan_init(ScanState *ss, TsFdwScanState *fsstate, Bitmapset *scanrelids, Lis rte = rt_fetch(rtindex, estate->es_range_table); - /* Get info about foreign server. */ - remote_connection_id_set(&id, - intVal(list_nth(fdw_private, FdwScanPrivateServerId)), - rte->checkAsUser ? rte->checkAsUser : GetUserId()); + remote_connection_id_set(&id, server_id, rte->checkAsUser ? rte->checkAsUser : GetUserId()); + + return remote_dist_txn_get_connection(id, + list_length(exprs) ? REMOTE_TXN_USE_PREP_STMT : + REMOTE_TXN_NO_PREP_STMT); +} + +void +fdw_scan_init(ScanState *ss, TsFdwScanState *fsstate, Bitmapset *scanrelids, List *fdw_private, + List *fdw_exprs, int eflags) +{ + int num_params; + + if ((eflags & EXEC_FLAG_EXPLAIN_ONLY) && !ts_guc_enable_remote_explain) + return; /* * Get connection to the foreign server. Connection manager will * establish new connection if necessary. */ - fsstate->conn = - remote_dist_txn_get_connection(id, - list_length(fdw_exprs) > 0 ? REMOTE_TXN_USE_PREP_STMT : - REMOTE_TXN_NO_PREP_STMT); + fsstate->conn = get_connection(ss, + intVal(list_nth(fdw_private, FdwScanPrivateServerId)), + scanrelids, + fdw_exprs); /* Get private info created by planner functions. */ fsstate->query = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); @@ -319,10 +319,68 @@ fdw_scan_end(TsFdwScanState *fsstate) /* MemoryContexts will be deleted automatically. */ } -void -fdw_scan_explain(ScanState *ss, List *fdw_private, ExplainState *es) +static char * +get_data_node_explain(const char *sql, TSConnection *conn, ExplainState *es) +{ + AsyncRequest *volatile req = NULL; + AsyncResponseResult *volatile res = NULL; + StringInfo explain_sql = makeStringInfo(); + StringInfo buf = makeStringInfo(); + + appendStringInfo(explain_sql, "%s", "EXPLAIN (VERBOSE "); + if (es->analyze) + appendStringInfo(explain_sql, "%s", ", ANALYZE"); + if (!es->costs) + appendStringInfo(explain_sql, "%s", ", COSTS OFF"); + if (es->buffers) + appendStringInfo(explain_sql, "%s", ", BUFFERS ON"); + if (!es->timing) + appendStringInfo(explain_sql, "%s", ", TIMING OFF"); + if (es->summary) + appendStringInfo(explain_sql, "%s", ", SUMMARY ON"); + else + appendStringInfo(explain_sql, "%s", ", SUMMARY OFF"); + + appendStringInfoChar(explain_sql, ')'); + + appendStringInfo(explain_sql, " %s", sql); + + PG_TRY(); + { + PGresult *pg_res; + int i; + + req = async_request_send(conn, explain_sql->data); + res = async_request_wait_ok_result(req); + pg_res = async_response_result_get_pg_result(res); + appendStringInfoChar(buf, '\n'); + + for (i = 0; i < PQntuples(pg_res); i++) + { + appendStringInfoSpaces(buf, (es->indent + 1) * 2); + appendStringInfo(buf, "%s\n", PQgetvalue(pg_res, i, 0)); + } + + pfree(req); + async_response_result_close(res); + } + PG_CATCH(); + { + if (req != NULL) + pfree(req); + if (res != NULL) + async_response_result_close(res); + + PG_RE_THROW(); + } + PG_END_TRY(); + + return buf->data; +} + +void +fdw_scan_explain(ScanState *ss, List *fdw_private, ExplainState *es, TsFdwScanState *fsstate) { - const char *sql; const char *relations; /* @@ -365,7 +423,15 @@ fdw_scan_explain(ScanState *ss, List *fdw_private, ExplainState *es) ExplainPropertyText("Chunks", chunk_names.data, es); } - sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)); - ExplainPropertyText("Remote SQL", sql, es); + ExplainPropertyText("Remote SQL", + strVal(list_nth(fdw_private, FdwScanPrivateSelectSql)), + es); + + if (ts_guc_enable_remote_explain) + { + const char *data_node_explain = + get_data_node_explain(fsstate->query, fsstate->conn, es); + ExplainPropertyText("Remote EXPLAIN", data_node_explain, es); + } } } diff --git a/tsl/src/fdw/scan_exec.h b/tsl/src/fdw/scan_exec.h index 7ae92c668..c8944b80b 100644 --- a/tsl/src/fdw/scan_exec.h +++ b/tsl/src/fdw/scan_exec.h @@ -42,7 +42,8 @@ extern void fdw_scan_init(ScanState *ss, TsFdwScanState *fsstate, Bitmapset *sca extern TupleTableSlot *fdw_scan_iterate(ScanState *ss, TsFdwScanState *fsstate); extern void fdw_scan_rescan(ScanState *ss, TsFdwScanState *fsstate); extern void fdw_scan_end(TsFdwScanState *fsstate); -extern void fdw_scan_explain(ScanState *ss, List *fdw_private, ExplainState *es); +extern void fdw_scan_explain(ScanState *ss, List *fdw_private, ExplainState *es, + TsFdwScanState *fsstate); extern void create_cursor(ScanState *ss, TsFdwScanState *fsstate, bool block); diff --git a/tsl/test/expected/hypertable_distributed-11.out b/tsl/test/expected/hypertable_distributed-11.out index 7bfcd3695..cf08206da 100644 --- a/tsl/test/expected/hypertable_distributed-11.out +++ b/tsl/test/expected/hypertable_distributed-11.out @@ -678,6 +678,175 @@ FROM disttable; 90 | 2.7 | 2.7 (9 rows) +-- Test remote explain +SET timescaledb.enable_remote_explain = ON; +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT max(temp) +FROM disttable; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Result + Output: $0 + InitPlan 1 (returns $0) + -> Limit + Output: disttable.temp + -> Custom Scan (AsyncAppend) + Output: disttable.temp + -> Merge Append + Sort Key: disttable_1.temp DESC + -> Custom Scan (DataNodeScan) on public.disttable disttable_1 + Output: disttable_1.temp + Data node: data_node_1 + Chunks: _hyper_1_1_dist_chunk, _hyper_1_4_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort + Output: _hyper_1_1_dist_chunk.temp + Sort Key: _hyper_1_1_dist_chunk.temp DESC + -> Append + -> Seq Scan on _timescaledb_internal._hyper_1_1_dist_chunk + Output: _hyper_1_1_dist_chunk.temp + Filter: (_hyper_1_1_dist_chunk.temp IS NOT NULL) + -> Seq Scan on _timescaledb_internal._hyper_1_4_dist_chunk + Output: _hyper_1_4_dist_chunk.temp + Filter: (_hyper_1_4_dist_chunk.temp IS NOT NULL) + + -> Custom Scan (DataNodeScan) on public.disttable disttable_2 + Output: disttable_2.temp + Data node: data_node_2 + Chunks: _hyper_1_3_dist_chunk, _hyper_1_5_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort + Output: _hyper_1_3_dist_chunk.temp + Sort Key: _hyper_1_3_dist_chunk.temp DESC + -> Append + -> Seq Scan on _timescaledb_internal._hyper_1_3_dist_chunk + Output: _hyper_1_3_dist_chunk.temp + Filter: (_hyper_1_3_dist_chunk.temp IS NOT NULL) + -> Seq Scan on _timescaledb_internal._hyper_1_5_dist_chunk + Output: _hyper_1_5_dist_chunk.temp + Filter: (_hyper_1_5_dist_chunk.temp IS NOT NULL) + + -> Custom Scan (DataNodeScan) on public.disttable disttable_3 + Output: disttable_3.temp + Data node: data_node_3 + Chunks: _hyper_1_2_dist_chunk, _hyper_1_6_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort + Output: _hyper_1_2_dist_chunk.temp + Sort Key: _hyper_1_2_dist_chunk.temp DESC + -> Append + -> Seq Scan on _timescaledb_internal._hyper_1_2_dist_chunk + Output: _hyper_1_2_dist_chunk.temp + Filter: (_hyper_1_2_dist_chunk.temp IS NOT NULL) + -> Seq Scan on _timescaledb_internal._hyper_1_6_dist_chunk + Output: _hyper_1_6_dist_chunk.temp + Filter: (_hyper_1_6_dist_chunk.temp IS NOT NULL) + +(60 rows) + +-- Don't remote explain if there is no VERBOSE flag +EXPLAIN (COSTS FALSE) +SELECT max(temp) +FROM disttable; + QUERY PLAN +------------------------------------------------------------------------------- + Result + InitPlan 1 (returns $0) + -> Limit + -> Custom Scan (AsyncAppend) + -> Merge Append + Sort Key: disttable_1.temp DESC + -> Custom Scan (DataNodeScan) on disttable disttable_1 + -> Custom Scan (DataNodeScan) on disttable disttable_2 + -> Custom Scan (DataNodeScan) on disttable disttable_3 +(9 rows) + +-- Test additional EXPLAIN flags +EXPLAIN (ANALYZE, VERBOSE, COSTS FALSE, BUFFERS ON, TIMING OFF, SUMMARY OFF) +SELECT max(temp) +FROM disttable; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Result (actual rows=1 loops=1) + Output: $0 + InitPlan 1 (returns $0) + -> Limit (actual rows=1 loops=1) + Output: disttable.temp + -> Custom Scan (AsyncAppend) (actual rows=1 loops=1) + Output: disttable.temp + -> Merge Append (actual rows=1 loops=1) + Sort Key: disttable_1.temp DESC + -> Custom Scan (DataNodeScan) on public.disttable disttable_1 (actual rows=1 loops=1) + Output: disttable_1.temp + Data node: data_node_1 + Chunks: _hyper_1_1_dist_chunk, _hyper_1_4_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort (actual rows=4 loops=1) + Output: _hyper_1_1_dist_chunk.temp + Sort Key: _hyper_1_1_dist_chunk.temp DESC + Sort Method: quicksort + Buffers: shared hit=2 + -> Append (actual rows=4 loops=1) + Buffers: shared hit=2 + -> Seq Scan on _timescaledb_internal._hyper_1_1_dist_chunk (actual rows=3 loops=1) + Output: _hyper_1_1_dist_chunk.temp + Filter: (_hyper_1_1_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + -> Seq Scan on _timescaledb_internal._hyper_1_4_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_4_dist_chunk.temp + Filter: (_hyper_1_4_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + + -> Custom Scan (DataNodeScan) on public.disttable disttable_2 (actual rows=1 loops=1) + Output: disttable_2.temp + Data node: data_node_2 + Chunks: _hyper_1_3_dist_chunk, _hyper_1_5_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort (actual rows=2 loops=1) + Output: _hyper_1_3_dist_chunk.temp + Sort Key: _hyper_1_3_dist_chunk.temp DESC + Sort Method: quicksort + Buffers: shared hit=2 + -> Append (actual rows=2 loops=1) + Buffers: shared hit=2 + -> Seq Scan on _timescaledb_internal._hyper_1_3_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_3_dist_chunk.temp + Filter: (_hyper_1_3_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + -> Seq Scan on _timescaledb_internal._hyper_1_5_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_5_dist_chunk.temp + Filter: (_hyper_1_5_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + + -> Custom Scan (DataNodeScan) on public.disttable disttable_3 (actual rows=1 loops=1) + Output: disttable_3.temp + Data node: data_node_3 + Chunks: _hyper_1_2_dist_chunk, _hyper_1_6_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort (actual rows=3 loops=1) + Output: _hyper_1_2_dist_chunk.temp + Sort Key: _hyper_1_2_dist_chunk.temp DESC + Sort Method: quicksort + Buffers: shared hit=2 + -> Append (actual rows=3 loops=1) + Buffers: shared hit=2 + -> Seq Scan on _timescaledb_internal._hyper_1_2_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_2_dist_chunk.temp + Filter: (_hyper_1_2_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + -> Seq Scan on _timescaledb_internal._hyper_1_6_dist_chunk (actual rows=2 loops=1) + Output: _hyper_1_6_dist_chunk.temp + Filter: (_hyper_1_6_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + +(75 rows) + -- The constraints, indexes, and triggers on foreign chunks. Only -- check constraints should recurse to foreign chunks (although they -- aren't enforced on a foreign table) diff --git a/tsl/test/expected/hypertable_distributed-12.out b/tsl/test/expected/hypertable_distributed-12.out index 2fdee7dc3..5034f960d 100644 --- a/tsl/test/expected/hypertable_distributed-12.out +++ b/tsl/test/expected/hypertable_distributed-12.out @@ -678,6 +678,175 @@ FROM disttable; 90 | 2.7 | 2.7 (9 rows) +-- Test remote explain +SET timescaledb.enable_remote_explain = ON; +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT max(temp) +FROM disttable; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Result + Output: $0 + InitPlan 1 (returns $0) + -> Limit + Output: disttable.temp + -> Custom Scan (AsyncAppend) + Output: disttable.temp + -> Merge Append + Sort Key: disttable_1.temp DESC + -> Custom Scan (DataNodeScan) on public.disttable disttable_1 + Output: disttable_1.temp + Data node: data_node_1 + Chunks: _hyper_1_1_dist_chunk, _hyper_1_4_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort + Output: _hyper_1_1_dist_chunk.temp + Sort Key: _hyper_1_1_dist_chunk.temp DESC + -> Append + -> Seq Scan on _timescaledb_internal._hyper_1_1_dist_chunk + Output: _hyper_1_1_dist_chunk.temp + Filter: (_hyper_1_1_dist_chunk.temp IS NOT NULL) + -> Seq Scan on _timescaledb_internal._hyper_1_4_dist_chunk + Output: _hyper_1_4_dist_chunk.temp + Filter: (_hyper_1_4_dist_chunk.temp IS NOT NULL) + + -> Custom Scan (DataNodeScan) on public.disttable disttable_2 + Output: disttable_2.temp + Data node: data_node_2 + Chunks: _hyper_1_3_dist_chunk, _hyper_1_5_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort + Output: _hyper_1_3_dist_chunk.temp + Sort Key: _hyper_1_3_dist_chunk.temp DESC + -> Append + -> Seq Scan on _timescaledb_internal._hyper_1_3_dist_chunk + Output: _hyper_1_3_dist_chunk.temp + Filter: (_hyper_1_3_dist_chunk.temp IS NOT NULL) + -> Seq Scan on _timescaledb_internal._hyper_1_5_dist_chunk + Output: _hyper_1_5_dist_chunk.temp + Filter: (_hyper_1_5_dist_chunk.temp IS NOT NULL) + + -> Custom Scan (DataNodeScan) on public.disttable disttable_3 + Output: disttable_3.temp + Data node: data_node_3 + Chunks: _hyper_1_2_dist_chunk, _hyper_1_6_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort + Output: _hyper_1_2_dist_chunk.temp + Sort Key: _hyper_1_2_dist_chunk.temp DESC + -> Append + -> Seq Scan on _timescaledb_internal._hyper_1_2_dist_chunk + Output: _hyper_1_2_dist_chunk.temp + Filter: (_hyper_1_2_dist_chunk.temp IS NOT NULL) + -> Seq Scan on _timescaledb_internal._hyper_1_6_dist_chunk + Output: _hyper_1_6_dist_chunk.temp + Filter: (_hyper_1_6_dist_chunk.temp IS NOT NULL) + +(60 rows) + +-- Don't remote explain if there is no VERBOSE flag +EXPLAIN (COSTS FALSE) +SELECT max(temp) +FROM disttable; + QUERY PLAN +------------------------------------------------------------------------------- + Result + InitPlan 1 (returns $0) + -> Limit + -> Custom Scan (AsyncAppend) + -> Merge Append + Sort Key: disttable_1.temp DESC + -> Custom Scan (DataNodeScan) on disttable disttable_1 + -> Custom Scan (DataNodeScan) on disttable disttable_2 + -> Custom Scan (DataNodeScan) on disttable disttable_3 +(9 rows) + +-- Test additional EXPLAIN flags +EXPLAIN (ANALYZE, VERBOSE, COSTS FALSE, BUFFERS ON, TIMING OFF, SUMMARY OFF) +SELECT max(temp) +FROM disttable; + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + Result (actual rows=1 loops=1) + Output: $0 + InitPlan 1 (returns $0) + -> Limit (actual rows=1 loops=1) + Output: disttable.temp + -> Custom Scan (AsyncAppend) (actual rows=1 loops=1) + Output: disttable.temp + -> Merge Append (actual rows=1 loops=1) + Sort Key: disttable_1.temp DESC + -> Custom Scan (DataNodeScan) on public.disttable disttable_1 (actual rows=1 loops=1) + Output: disttable_1.temp + Data node: data_node_1 + Chunks: _hyper_1_1_dist_chunk, _hyper_1_4_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort (actual rows=4 loops=1) + Output: _hyper_1_1_dist_chunk.temp + Sort Key: _hyper_1_1_dist_chunk.temp DESC + Sort Method: quicksort + Buffers: shared hit=2 + -> Append (actual rows=4 loops=1) + Buffers: shared hit=2 + -> Seq Scan on _timescaledb_internal._hyper_1_1_dist_chunk (actual rows=3 loops=1) + Output: _hyper_1_1_dist_chunk.temp + Filter: (_hyper_1_1_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + -> Seq Scan on _timescaledb_internal._hyper_1_4_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_4_dist_chunk.temp + Filter: (_hyper_1_4_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + + -> Custom Scan (DataNodeScan) on public.disttable disttable_2 (actual rows=1 loops=1) + Output: disttable_2.temp + Data node: data_node_2 + Chunks: _hyper_1_3_dist_chunk, _hyper_1_5_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort (actual rows=2 loops=1) + Output: _hyper_1_3_dist_chunk.temp + Sort Key: _hyper_1_3_dist_chunk.temp DESC + Sort Method: quicksort + Buffers: shared hit=2 + -> Append (actual rows=2 loops=1) + Buffers: shared hit=2 + -> Seq Scan on _timescaledb_internal._hyper_1_3_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_3_dist_chunk.temp + Filter: (_hyper_1_3_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + -> Seq Scan on _timescaledb_internal._hyper_1_5_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_5_dist_chunk.temp + Filter: (_hyper_1_5_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + + -> Custom Scan (DataNodeScan) on public.disttable disttable_3 (actual rows=1 loops=1) + Output: disttable_3.temp + Data node: data_node_3 + Chunks: _hyper_1_2_dist_chunk, _hyper_1_6_dist_chunk + Remote SQL: SELECT temp FROM public.disttable WHERE _timescaledb_internal.chunks_in(disttable, ARRAY[1, 2]) AND ((temp IS NOT NULL)) ORDER BY temp DESC NULLS FIRST + Remote EXPLAIN: + Sort (actual rows=3 loops=1) + Output: _hyper_1_2_dist_chunk.temp + Sort Key: _hyper_1_2_dist_chunk.temp DESC + Sort Method: quicksort + Buffers: shared hit=2 + -> Append (actual rows=3 loops=1) + Buffers: shared hit=2 + -> Seq Scan on _timescaledb_internal._hyper_1_2_dist_chunk (actual rows=1 loops=1) + Output: _hyper_1_2_dist_chunk.temp + Filter: (_hyper_1_2_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + -> Seq Scan on _timescaledb_internal._hyper_1_6_dist_chunk (actual rows=2 loops=1) + Output: _hyper_1_6_dist_chunk.temp + Filter: (_hyper_1_6_dist_chunk.temp IS NOT NULL) + Buffers: shared hit=1 + +(75 rows) + -- The constraints, indexes, and triggers on foreign chunks. Only -- check constraints should recurse to foreign chunks (although they -- aren't enforced on a foreign table) diff --git a/tsl/test/sql/hypertable_distributed.sql.in b/tsl/test/sql/hypertable_distributed.sql.in index 55233ec50..b2c6c4bca 100644 --- a/tsl/test/sql/hypertable_distributed.sql.in +++ b/tsl/test/sql/hypertable_distributed.sql.in @@ -239,6 +239,24 @@ FROM disttable; SELECT device, temp, avg(temp) OVER (PARTITION BY device) FROM disttable; +-- Test remote explain + +SET timescaledb.enable_remote_explain = ON; + +EXPLAIN (VERBOSE, COSTS FALSE) +SELECT max(temp) +FROM disttable; + +-- Don't remote explain if there is no VERBOSE flag +EXPLAIN (COSTS FALSE) +SELECT max(temp) +FROM disttable; + +-- Test additional EXPLAIN flags +EXPLAIN (ANALYZE, VERBOSE, COSTS FALSE, BUFFERS ON, TIMING OFF, SUMMARY OFF) +SELECT max(temp) +FROM disttable; + -- The constraints, indexes, and triggers on foreign chunks. Only -- check constraints should recurse to foreign chunks (although they -- aren't enforced on a foreign table)