Skip to content

Commit 3547364

Browse files
committed
Improved query planning
1 parent 7024d81 commit 3547364

8 files changed

Lines changed: 251 additions & 21 deletions

src/executor/multi_phase_executor.c

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,11 @@ static void ConstructPostProcessingPhase(CoordinatorLevelOperator *coordOp, Mult
3636

3737

3838

39-
/* QueryExecutor */
39+
/*
40+
* QueryExecutor drives a distributed query end-to-end: runs every planned
41+
* strategy via RunQueryExecutor(), then stitches the resulting per-strategy
42+
* tasks into the single SQL statement returned as a GeneralScan.
43+
*/
4044
extern GeneralScan *
4145
QueryExecutor(DistributedSpatiotemporalQueryPlan *distPlan, bool explain)
4246
{
@@ -45,7 +49,15 @@ QueryExecutor(DistributedSpatiotemporalQueryPlan *distPlan, bool explain)
4549
return generalScan;
4650
}
4751

48-
/* RunQueryExecutor */
52+
/*
53+
* RunQueryExecutor walks distPlan's chosen PlanTask strategies in order and
54+
* materializes each into ExecutorTasks: NonColocation triggers a reshuffle
55+
* of one side into a temporary colocated table followed by a neighbor scan,
56+
* Colocation performs a direct self-tiling (tile-key equi-join) scan, and
57+
* PredicatePushDown runs the predicate as-is on the worker. `explain`
58+
* suppresses the actual reshuffle/data-modifying SPI calls so EXPLAIN can
59+
* describe the plan without side effects.
60+
*/
4961
extern MultiPhaseExecutor *
5062
RunQueryExecutor(DistributedSpatiotemporalQueryPlan *distPlan, bool explain)
5163
{
@@ -102,7 +114,13 @@ RunQueryExecutor(DistributedSpatiotemporalQueryPlan *distPlan, bool explain)
102114
}
103115

104116

105-
/* Executor Job: Colocation */
117+
/*
118+
* ColocateRte makes a plain Citus-distributed `other` table colocated with
119+
* `base`: it materializes other's data into a fresh reshuffled table
120+
* distributed on base's distribution column/shard count, then rebalances
121+
* base's tiles into it so both sides can be scanned tile-by-tile without
122+
* cross-node data movement. Returns false if `other` is not a CitusRte.
123+
*/
106124
extern bool
107125
ColocateRte(STMultirelation *base, Rte *other)
108126
{
@@ -142,7 +160,12 @@ ColocateRte(STMultirelation *base, Rte *other)
142160

143161
}
144162

145-
/* Executor Job: Create the reshuffling table */
163+
/*
164+
* createReshuffledTable is the STRte counterpart of ColocateRte(): it
165+
* materializes `other`'s data into a fresh table distributed to match
166+
* `base`'s shard count, then rebalances base's tiles into it so both
167+
* spatiotemporal relations can be joined tile-by-tile.
168+
*/
146169
extern bool
147170
createReshuffledTable(STMultirelation *base, STMultirelation *other)
148171
{
@@ -175,7 +198,13 @@ createReshuffledTable(STMultirelation *base, STMultirelation *other)
175198
return true;
176199
}
177200

178-
/* Executor Job: Drop the reshuffled table if exists */
201+
/*
202+
* DropReshuffledTableIfExists drops the temporary per-query reshuffled
203+
* table from the extension's schema. The surrounding
204+
* commit/start-transaction pair runs the DDL in its own transaction so it
205+
* takes effect immediately and is visible to the CREATE that follows it,
206+
* rather than staying pending inside the planner's outer transaction.
207+
*/
179208
extern void
180209
DropReshuffledTableIfExists(char * reshuffled_table)
181210
{
@@ -192,7 +221,13 @@ DropReshuffledTableIfExists(char * reshuffled_table)
192221
PushActiveSnapshot(GetTransactionSnapshot());
193222
}
194223

195-
/* Executor Job: Create the reshuffled table if not exists */
224+
/*
225+
* CreateReshuffledTableIfNotExists creates reshuffled_table as a `LIKE
226+
* org_table` copy in the extension's schema, optionally adding the tile-key
227+
* column (for tables that will be reshuffled/rebalanced across tiles). Runs
228+
* in its own commit/start-transaction bracket for the same reason as
229+
* DropReshuffledTableIfExists.
230+
*/
196231
extern void
197232
CreateReshuffledTableIfNotExists(char * reshuffled_table, char * org_table, bool tile_key)
198233
{
@@ -212,7 +247,12 @@ CreateReshuffledTableIfNotExists(char * reshuffled_table, char * org_table, bool
212247
PushActiveSnapshot(GetTransactionSnapshot());
213248
}
214249

215-
/* Executor Job: Construct the neighbor scan */
250+
/*
251+
* ConstructNeighborScanQuery builds the task that scans `tbl` (the
252+
* reshuffled/colocated side) tile-by-tile against `base`'s tiling scheme,
253+
* widening the tile set searched (catalog_filtered) whenever the catalog
254+
* filter found more matching candidates than there are tiles.
255+
*/
216256
static void
217257
ConstructNeighborScanQuery(Rte *tbl, char * query_string, STMultirelation *base,MultiPhaseExecutor *multiPhaseExecutor)
218258
{
@@ -243,7 +283,13 @@ ConstructNeighborScanQuery(Rte *tbl, char * query_string, STMultirelation *base,
243283
multiPhaseExecutor->tasks = lappend(multiPhaseExecutor->tasks, task);
244284
}
245285

246-
/* Executor Job: Construct the self tiling scan */
286+
/*
287+
* ConstructSelfTilingScanQuery builds the task for a Colocation-strategy
288+
* join: since both tables share the same tiling, the join reduces to
289+
* adding a `tile_key = tile_key` equality (replacing the query's `WHERE`)
290+
* so each tile only ever matches its own counterpart tile, avoiding any
291+
* cross-tile data transfer.
292+
*/
247293
static void
248294
ConstructSelfTilingScanQuery(PlanTask *plan, char * query_string, MultiPhaseExecutor *multiPhaseExecutor)
249295
{
@@ -269,7 +315,11 @@ ConstructSelfTilingScanQuery(PlanTask *plan, char * query_string, MultiPhaseExec
269315
multiPhaseExecutor->tasks = lappend(multiPhaseExecutor->tasks, task);
270316
}
271317

272-
/* Executor Job: Execute the reshuffling query */
318+
/*
319+
* ReshuffleData runs the INSERT ... SELECT that copies base's rows into the
320+
* reshuffled/colocated table (see createReshuffledTable/ColocateRte),
321+
* recording success in multiPhaseExecutor->dataReshuffled.
322+
*/
273323
static void
274324
ReshuffleData(char *query_string, MultiPhaseExecutor *multiPhaseExecutor)
275325
{
@@ -293,7 +343,11 @@ ReshuffleData(char *query_string, MultiPhaseExecutor *multiPhaseExecutor)
293343
}
294344
}
295345

296-
/* Executor Job: Index the reshuffled data */
346+
/*
347+
* IndexReshuffledData creates the spatiotemporal (GIST) index on the newly
348+
* reshuffled table's distribution column, needed before it can be scanned
349+
* efficiently in the neighbor-scan phase.
350+
*/
297351
static void
298352
IndexReshuffledData(Rte *reshuffledTable, MultiPhaseExecutor *multiPhaseExecutor)
299353
{
@@ -339,7 +393,14 @@ IndexReshuffledData(Rte *reshuffledTable, MultiPhaseExecutor *multiPhaseExecutor
339393

340394
}
341395

342-
/* Executor Job: GeneralScan */
396+
/*
397+
* ConstructGeneralQuery assembles the final SQL text to execute: it unions
398+
* together the worker-phase task query for each strategy used in the plan
399+
* (NonColocation -> neighbor scan, Colocation -> self-tiling scan,
400+
* PredicatePushDown -> push-down scan), then, if a coordinator-phase
401+
* (FINALScan) task exists, splices that union in as its `intermediate`
402+
* subquery so the coordinator can post-process the combined worker output.
403+
*/
343404
static GeneralScan *
344405
ConstructGeneralQuery(DistributedSpatiotemporalQueryPlan *distPlan, MultiPhaseExecutor *multiPhaseExecutor)
345406
{
@@ -400,7 +461,12 @@ ConstructGeneralQuery(DistributedSpatiotemporalQueryPlan *distPlan, MultiPhaseEx
400461
return generalScan;
401462
}
402463

403-
/* Executor Job: Construct the self tiling scan */
464+
/*
465+
* ConstructPredicatePushDownQuery builds the task for the PredicatePushDown
466+
* strategy: the original query text is run as-is (no rewriting), since the
467+
* predicate can be fully evaluated on the worker without any coordinator
468+
* merge step.
469+
*/
404470
static void
405471
ConstructPredicatePushDownQuery(PlanTask *plan, char * query_string, MultiPhaseExecutor *multiPhaseExecutor)
406472
{
@@ -413,6 +479,7 @@ ConstructPredicatePushDownQuery(PlanTask *plan, char * query_string, MultiPhaseE
413479
multiPhaseExecutor->tasks = lappend(multiPhaseExecutor->tasks, task);
414480
}
415481

482+
/* GetTaskType renders task's ExecTaskType as a human-readable label for EXPLAIN output. */
416483
extern Datum
417484
GetTaskType(ExecutorTask *task)
418485
{
@@ -422,6 +489,12 @@ GetTaskType(ExecutorTask *task)
422489
return CStringGetDatum("Self Tiling Scan");
423490
}
424491

492+
/*
493+
* ConstructPostProcessingPhase builds the coordinator-phase tasks (see
494+
* ProcessIntermediateTasks/ProcessFinalTasks) for any distributed
495+
* aggregates recorded in coordOp, so they get merged into the final query
496+
* by ConstructGeneralQuery().
497+
*/
425498
static void
426499
ConstructPostProcessingPhase(CoordinatorLevelOperator *coordOp, MultiPhaseExecutor *multiPhaseExecutor)
427500
{

src/executor/tile_tasks.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include "general/rte.h"
2222
#include "utils/planner_utils.h"
2323

24+
/* taskQuery returns the SQL text of the first task in `tasks` matching taskType. */
2425
char *
2526
taskQuery (List *tasks, ExecTaskType taskType)
2627
{
@@ -35,7 +36,12 @@ taskQuery (List *tasks, ExecTaskType taskType)
3536
}
3637
}
3738

38-
/* Executor Job: Rearrange the generated tiles */
39+
/*
40+
* RearrangeTiles calls the create_reshuffled_multirelation() SQL helper to
41+
* redistribute relid's rows into numTiles tiles inside reshuffledTable, so
42+
* two previously non-colocated tables end up sharing the same tiling
43+
* scheme before being joined.
44+
*/
3945
void
4046
RearrangeTiles(Oid relid, int numTiles, char *reshuffledTable)
4147
{
@@ -56,6 +62,12 @@ RearrangeTiles(Oid relid, int numTiles, char *reshuffledTable)
5662
PushActiveSnapshot(GetTransactionSnapshot());
5763
}
5864

65+
/*
66+
* AddTilingKey rewrites query_string so it targets the reshuffled table
67+
* (dist_mobilitydb.<reshuffledTable> instead of the original table name)
68+
* and replaces its `where` clause with a tile-key equality predicate,
69+
* ensuring the neighbor scan only compares rows sharing the same tile.
70+
*/
5971
Datum
6072
AddTilingKey(STMultirelationCatalog tblCatalog, Alias *alias ,char * query_string)
6173
{
@@ -71,6 +83,7 @@ AddTilingKey(STMultirelationCatalog tblCatalog, Alias *alias ,char * query_strin
7183
return CStringGetDatum(replaceWord( replaceWord(task_prep->data,"where", tmp->data), ";", " "));
7284
}
7385

86+
/* AddNonStRteTilingKey is AddTilingKey()'s counterpart for a plain Citus-distributed (non-spatiotemporal) relation. */
7487
Datum
7588
AddNonStRteTilingKey(Rte *tbl, Alias *alias ,char * query_string)
7689
{
@@ -92,6 +105,7 @@ AddNonStRteTilingKey(Rte *tbl, Alias *alias ,char * query_string)
92105
return 0;
93106
}
94107

108+
/* GetRandTileNum returns a randomly-chosen shard's shardminvalue for rte, used to sample a single tile. */
95109
extern int
96110
GetRandTileNum(STMultirelation *rte)
97111
{

src/planner/distributed_mobilitydb_explain.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ RegisterSpatiotemporalPlanMethods(void)
9393
RegisterCustomScanMethods(&SpatiotemporalExecutorMethod);
9494
}
9595

96+
/* SpatiotemporalExecutorCreateScan is the CustomScanMethods callback that builds the scan's execution state. */
9697
static Node *
9798
SpatiotemporalExecutorCreateScan(CustomScan *scan)
9899
{

src/planner/distributed_mobilitydb_planner.c

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,13 @@ static bool needsDistributedSpatiotemporalPlanning(DistributedSpatiotemporalQuer
4141
static PlannedStmt * EarlyQueryCheck(Query *parse, const char *query_string, int cursorOptions,
4242
ParamListInfo boundParams);
4343

44-
/* Distributed spatiotemporal planner */
44+
/*
45+
* distributed_mobilitydb_planner is the planner_hook entry point (see
46+
* _PG_init in shared_library_init.c): it allocates a fresh
47+
* DistributedSpatiotemporalQueryPlan and delegates the actual work to
48+
* distributed_mobilitydb_planner_internal(), which is also reused directly
49+
* by the EXPLAIN hook.
50+
*/
4551
PlannedStmt *
4652
distributed_mobilitydb_planner(Query *parse, const char *query_string, int cursorOptions,
4753
ParamListInfo boundParams)
@@ -52,6 +58,18 @@ distributed_mobilitydb_planner(Query *parse, const char *query_string, int curso
5258
distributedSpatiotemporalPlan, false);
5359
}
5460

61+
/*
62+
* distributed_mobilitydb_planner_internal drives the full planning
63+
* pipeline for a query: bail out early (via EarlyQueryCheck/Citus'
64+
* distributed_planner) when no distributed spatiotemporal table is
65+
* involved; otherwise inventory the query's tables
66+
* (analyzeDistributedSpatiotemporalTables), classify its predicates
67+
* (checkQueryType) to pick execution strategies, rewrite any distributed
68+
* aggregate calls (RewriterDistFuncs), run each chosen strategy's plan
69+
* function, and finally hand the rewritten query off to QueryExecutor()
70+
* and Citus' planner. `explain` skips the actual execution step so EXPLAIN
71+
* can report the plan without running it.
72+
*/
5573
PlannedStmt *
5674
distributed_mobilitydb_planner_internal(Query *parse, const char *query_string, int cursorOptions,
5775
ParamListInfo boundParams,
@@ -240,6 +258,13 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
240258
distPlan->tablesList->tables = rtes;
241259
}
242260

261+
/*
262+
* EarlyQueryCheck short-circuits planning for queries that don't touch any
263+
* distributed spatiotemporal SELECT-able table: it returns a plan produced
264+
* by Citus' standard distributed_planner() immediately, or NULL to let
265+
* distributed_mobilitydb_planner_internal() continue with the
266+
* spatiotemporal-aware planning path.
267+
*/
243268
static PlannedStmt *
244269
EarlyQueryCheck(Query *parse, const char *query_string, int cursorOptions, ParamListInfo boundParams)
245270
{
@@ -318,10 +343,13 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
318343
{
319344
if (distPlan->tablesList->diffCount > 1)
320345
{
346+
/* Intersection join between two distinct tables: must colocate them first. */
321347
AddStrategy(distPlan, NonColocation);
322348
}
323349
else if (distPlan->tablesList->length == 1)
324350
{
351+
/* Single-table intersection: decide between rebalancing tiles to fit the
352+
* query's search box or simply pushing the predicate to each worker. */
325353
Datum rangeBox = get_query_range(distPlan->tablesList, opExpr);
326354
if (!IsDatumEmpty(rangeBox) &&
327355
CheckTileRebalancerActivation(distPlan->tablesList, opExpr, rangeBox))
@@ -334,7 +362,7 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
334362
}
335363
else
336364
{
337-
/* By default */
365+
/* By default: multiple references to the same colocated table (self-join). */
338366
AddStrategy(distPlan, Colocation);
339367
}
340368
}

0 commit comments

Comments
 (0)