Skip to content

Commit 3b80b0f

Browse files
committed
fixed tile allocation issue
1 parent 1fa0e5a commit 3b80b0f

5 files changed

Lines changed: 134 additions & 21 deletions

File tree

include/multirelation/multirelation_utils.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,11 @@ typedef struct STMultirelations
9292
int stCount;
9393
int nonStCount;
9494
int length;
95+
/* Count of nonStCount entries that are Citus reference tables -- these
96+
* are already replicated to every node, so they never need reshuffling
97+
* and shouldn't count as a "different distributed table" when deciding
98+
* whether a join needs the NonColocation strategy. */
99+
int refCount;
95100
} STMultirelations;
96101

97102
/* True if relationId is registered as a distributed spatiotemporal table. */

src/catalog/table_ops.c

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -258,18 +258,23 @@ GetShapeCol(Oid relationId)
258258
get_rel_name(relationId));
259259

260260
spi_result = SPI_execute(catalogQuery->data, true, 1);
261-
/* Read back the PROJ text */
262-
if (spi_result == SPI_OK_SELECT)
261+
/* Read back the PROJ text. getDistributedCol() legitimately returns
262+
* NULL for a plain (non-spatiotemporal) table, e.g. a reference table
263+
* joined alongside a distributed one -- calling DatumToString on that
264+
* NULL Datum crashed instead of just reporting "no shape column". */
265+
char *result = NULL;
266+
if (spi_result == SPI_OK_SELECT && SPI_processed > 0)
263267
{
264268
TupleDesc rowDescriptor = SPI_tuptable->tupdesc;
265269
HeapTuple row = SPI_copytuple(SPI_tuptable->vals[0]);
266270
Datum distcol = SPI_getbinval(row, rowDescriptor, 1, &isNull);
267-
spi_result = SPI_finish();
268-
if (spi_result != SPI_OK_FINISH)
269-
{
270-
elog(ERROR, "Could not disconnect from database using SPI");
271-
}
272-
return DatumToString(distcol, TEXTOID);;
271+
if (!isNull)
272+
result = DatumToString(distcol, TEXTOID);
273273
}
274-
return NULL;
274+
spi_result = SPI_finish();
275+
if (spi_result != SPI_OK_FINISH)
276+
{
277+
elog(ERROR, "Could not disconnect from database using SPI");
278+
}
279+
return result;
275280
}

src/general/shared_library_init.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "postgres.h"
1616
#include "miscadmin.h"
1717
#include "utils/elog.h"
18+
#include "utils/guc.h"
1819
#include "commands/explain.h"
1920
#include "planner/distributed_mobilitydb_planner.h"
2021
#include "planner/distributed_mobilitydb_explain.h"
@@ -36,4 +37,12 @@ _PG_init(void)
3637
planner_hook = distributed_mobilitydb_planner;
3738

3839
ExplainOneQuery_hook = distributed_mobilitydb_explain;
40+
41+
/* Cross-table joins built by this extension's NonColocation/Colocation
42+
* strategies aren't guaranteed to be physically co-located (see
43+
* colocate_shards()), so Citus plans them as repartition joins. Turn
44+
* that on as a session default here so users don't have to discover and
45+
* set it themselves before a join query works; they can still override
46+
* it with their own SET/RESET afterwards. */
47+
SetConfigOption("citus.enable_repartition_joins", "on", PGC_SUSET, PGC_S_SESSION);
3948
}

src/planner/distributed_mobilitydb_planner.c

Lines changed: 79 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,21 @@ distributed_mobilitydb_planner_internal(Query *parse, const char *query_string,
122122
/* Query rewriter */
123123
if (list_length(distPlan->postProcessing->distfuns) > 0 )
124124
RewriterDistFuncs(parse, distPlan->postProcessing, query_string);
125+
if (needsSpatiotemporalPlanning && list_length(distPlan->strategies) == 0
126+
&& list_length(distPlan->postProcessing->distfuns) == 0)
127+
{
128+
/* needsDistributedSpatiotemporalPlanning() can return true purely
129+
* from tablesList->diffCount > 1, even when checkQueryType() found
130+
* no registered intersection/distance predicate to build a strategy
131+
* for (e.g. a plain `@>` "contains" clause against a reference
132+
* table isn't one of those). With no strategy and no distributed
133+
* function, there is nothing for the custom executor below to
134+
* build a plan from -- it would otherwise fall into
135+
* ConstructGeneralQuery's `generalScan->length == 0` branch and use
136+
* postProcessing->worker, which is NULL here, producing "(null)"
137+
* as the query text. Let Citus plan the query directly instead. */
138+
return distributed_planner(parse, query_string, cursorOptions, boundParams);
139+
}
125140
if (needsSpatiotemporalPlanning)
126141
{
127142
if (!distPlan->activate_rewriter)
@@ -198,6 +213,13 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
198213
{
199214
ListCell *rangeTableCell = NULL;
200215
Oid curr_relid = -1;
216+
/* diffCount/simCount need to know whether relid has appeared ANYWHERE
217+
* earlier in the range table, not just in the immediately preceding
218+
* entry -- comparing only to curr_relid miscounted a self-join like
219+
* "Trips t1, Licences1 l1, Trips t2" as three different tables instead
220+
* of recognizing t2 as a repeat of t1, since t2 is compared against
221+
* l1's relid rather than t1's. */
222+
List *seenRelids = NIL;
201223
bool shapeType;
202224
List *rtes = NIL;
203225
foreach(rangeTableCell, rangeTableList)
@@ -206,6 +228,21 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
206228
if (rangeTableEntry->rtekind != RTE_RELATION) {
207229
continue;
208230
}
231+
/* A view reference (e.g. Licences1, a view over Licences) expands
232+
* in the range table into the view's own subquery entry (rtekind
233+
* != RTE_RELATION, already skipped above), PostgreSQL's internal
234+
* rule-system "old"/"new" placeholder entries for that view, and
235+
* the view's real underlying base table -- none of which the user
236+
* actually joined except the last. inFromCl is false for exactly
237+
* those non-user-visible placeholders (verified: "old"/"new" have
238+
* inFromCl=0, the real base table has inFromCl=1), so without this
239+
* check every view reference was triple-counted as extra distinct
240+
* tables, inflating diffCount/length enough to make e.g. a query
241+
* with one distributed table plus reference tables look like it
242+
* had several more distributed tables than it really did. */
243+
if (!rangeTableEntry->inFromCl) {
244+
continue;
245+
}
209246
if (IsDistributedSpatiotemporalTable(rangeTableEntry->relid))
210247
{
211248
if(IsReshuffledTable(rangeTableEntry->relid))
@@ -248,15 +285,29 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
248285
if (LookupCitusTableCacheEntry(rangeTableEntry->relid) != NULL)
249286
{
250287
char partitioningMethod = PartitionMethodViaCatalog (rangeTableEntry->relid);
251-
if (partitioningMethod == DISTRIBUTE_BY_HASH || partitioningMethod == DISTRIBUTE_BY_RANGE)
288+
/* A reference table is replicated to every node, so joining
289+
* it alongside a distributed table needs no repartitioning.
290+
* Its reported partitioning method alone doesn't identify it
291+
* uniquely, so check its table type explicitly instead. */
292+
bool isReferenceTable = IsCitusTableType(rangeTableEntry->relid, REFERENCE_TABLE);
293+
if (partitioningMethod == DISTRIBUTE_BY_HASH || partitioningMethod == DISTRIBUTE_BY_RANGE
294+
|| isReferenceTable)
252295
{
253296
/* Citus table processing */
254297
CitusRteNode *citusNode = GetCitusRteInfo(rangeTableEntry,partitioningMethod);
255298
citusNode->rangeTableCell = rangeTableCell;
256-
distPlan->tablesList->length++;
299+
/* length is already incremented unconditionally for
300+
* every range table entry below (after this if/else) --
301+
* incrementing it here too double-counted every Citus
302+
* table entry, inflating effectiveLength enough that a
303+
* query with one distributed table plus reference
304+
* tables was never recognized as an effective
305+
* single-table case. */
257306
Rte *rteNode = GetRteNode((Node *) citusNode, CitusRte, rangeTableEntry->alias);
258307
rtes = lappend(rtes , rteNode);
259308
distPlan->tablesList->nonStCount++;
309+
if (isReferenceTable)
310+
distPlan->tablesList->refCount++;
260311
}
261312
else
262313
elog(ERROR, "The %s table is not distributed using one of the supported partitioning methods",
@@ -270,9 +321,10 @@ analyzeDistributedSpatiotemporalTables(List *rangeTableList,
270321
rtes = lappend(rtes , rteNode);
271322
}
272323
}
273-
if (curr_relid != rangeTableEntry->relid)
324+
if (!list_member_oid(seenRelids, rangeTableEntry->relid))
274325
{
275326
distPlan->tablesList->diffCount++;
327+
seenRelids = lappend_oid(seenRelids, rangeTableEntry->relid);
276328
}
277329
else
278330
distPlan->tablesList->simCount++;
@@ -355,6 +407,14 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
355407
/* TODO: subquery is excluded for now */
356408
ereport(ERROR, (errmsg("A sub query is not supported yet in Distributed MobilityDB!")));
357409
}
410+
/* Reference tables are already replicated to every node, so a join
411+
* against one never needs the NonColocation strategy's reshuffle --
412+
* Citus can push the predicate down to each shard directly. Subtracting
413+
* refCount here means a query joining one distributed spatiotemporal
414+
* table with any number of reference tables is treated the same as a
415+
* genuine single-table query below. */
416+
int effectiveDiffCount = distPlan->tablesList->diffCount - distPlan->tablesList->refCount;
417+
int effectiveLength = distPlan->tablesList->length - distPlan->tablesList->refCount;
358418
/* Iterate over the where clause conditions */
359419
foreach(clauseCell, whereClauseList)
360420
{
@@ -379,12 +439,12 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
379439
{
380440
if (IsIntersectionOperation(predicateOid))
381441
{
382-
if (distPlan->tablesList->diffCount > 1)
442+
if (effectiveDiffCount > 1)
383443
{
384444
/* Intersection join between two distinct tables: must colocate them first. */
385445
AddStrategy(distPlan, NonColocation);
386446
}
387-
else if (distPlan->tablesList->length == 1)
447+
else if (effectiveLength == 1)
388448
{
389449
/* Single-table intersection: decide between rebalancing tiles to fit the
390450
* query's search box or simply pushing the predicate to each worker. */
@@ -406,10 +466,22 @@ checkQueryType(Query *parse, DistributedSpatiotemporalQueryPlan *distPlan)
406466
}
407467
else if (IsDistanceOperation(predicateOid))
408468
{
409-
/* The NonColocation strategy is triggered by default until the analysis changes it */
410469
if (distPlan->tablesList->simCount >= 1)
411470
AddStrategy(distPlan, Colocation);
412-
AddStrategy(distPlan, NonColocation);
471+
/* The NonColocation strategy is triggered by default until the analysis
472+
* changes it -- except when the only "different" tables besides one
473+
* distributed spatiotemporal table are reference tables (refCount > 0
474+
* guards this so behavior is untouched whenever no reference table is
475+
* involved), which Citus can push the predicate down to directly with
476+
* no reshuffle needed. */
477+
if (effectiveDiffCount > 1 || distPlan->tablesList->refCount == 0)
478+
{
479+
AddStrategy(distPlan, NonColocation);
480+
}
481+
else if (effectiveLength == 1)
482+
{
483+
AddStrategy(distPlan, PredicatePushDown);
484+
}
413485
if(distPlan->predicatesList->predicateType == DISTANCE)
414486
{
415487
ereport(ERROR, (errmsg("Currently, we do not support using more than "

src/planner/planner_strategies.c

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -451,18 +451,40 @@ getReshuffledColumns(DistributedSpatiotemporalQueryPlan *distPlan, Oid oid)
451451
}
452452

453453
/*
454-
* ColocationStrategyPlan records a Colocation PlanTask for the query's
455-
* first two range-table entries, joined on their shared tile key — no data
456-
* movement needed since both are already tiled the same way.
454+
* ColocationStrategyPlan records a Colocation PlanTask for the query's two
455+
* self-joined spatiotemporal (STRte) range-table entries, joined on their
456+
* shared tile key — no data movement needed since both are already tiled
457+
* the same way.
457458
*/
458459
extern void
459460
ColocationStrategyPlan(DistributedSpatiotemporalQueryPlan *distPlan)
460461
{
461462
PlanTask * strategy = (PlanTask *) palloc0(sizeof(PlanTask));
462463
strategy->type = Colocation;
463464
/* TODO: Add the other cases */
464-
strategy->tbl1 = (STMultirelation *) ((Rte *)list_nth(distPlan->tablesList->tables, 0))->rte;
465-
strategy->tbl2 = (STMultirelation *) ((Rte *)list_nth(distPlan->tablesList->tables, 1))->rte;
465+
/* This is a self-join, so the two tables to join are the two STRte
466+
* entries specifically -- not just whichever entries happen to be
467+
* first/second in tablesList->tables. Any reference or plain Citus
468+
* tables also joined in the same query (CitusRte/LocalRte entries)
469+
* can end up interleaved with them (e.g. "Trips t1, Licences1 l1,
470+
* Trips t2" puts l1 at index 1), and blindly casting one of those to
471+
* STMultirelation* read garbage through the wrong struct layout. */
472+
ListCell *rangeTableCell = NULL;
473+
STMultirelation *stTables[2] = {NULL, NULL};
474+
int stTableCount = 0;
475+
foreach(rangeTableCell, distPlan->tablesList->tables)
476+
{
477+
Rte *rteNode = (Rte *) lfirst(rangeTableCell);
478+
if (rteNode->RteType == STRte && stTableCount < 2)
479+
{
480+
stTables[stTableCount] = (STMultirelation *) rteNode->rte;
481+
stTableCount++;
482+
}
483+
}
484+
if (stTableCount < 2)
485+
ereport(ERROR, (errmsg("Colocation strategy requires two spatiotemporal tables to self-join")));
486+
strategy->tbl1 = stTables[0];
487+
strategy->tbl2 = stTables[1];
466488
strategy->tileKey = (Datum) Var_Catalog_Tile_Key;
467489
distPlan->strategyPlans = lappend(distPlan->strategyPlans, strategy);
468490
}

0 commit comments

Comments
 (0)