Skip to content

Commit 1fa0e5a

Browse files
committed
Fixed execution bug
1 parent 6073e7c commit 1fa0e5a

6 files changed

Lines changed: 281 additions & 66 deletions

File tree

include/catalog/nodes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ extern Datum GetDBName();
2323
/* Coordinator/worker node info (host, port, role) for the local backend. */
2424
extern TaskNode *GetNodeInfo();
2525

26+
/* Node (host, port) actually hosting relationId's shard for rand_tile. */
27+
extern TaskNode *GetShardHostNode(Oid relationId, int rand_tile);
28+
2629
/* Table id of the tile assigned to a randomly-picked worker for relationId. */
2730
extern char* GetRandomTileId(Oid relationId, ExecTaskType taskType, int rand_tile);
2831

sql/helper_functions/colocation_utils.sql

Lines changed: 31 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,47 +34,38 @@ END;
3434
$$;
3535

3636
----------------------------------------------------------------------------------------------------------------------
37-
-- colocate_multirelation colocates one multirelation with another
37+
-- colocate_shards physically moves table2's shards so each one lands on the
38+
-- same node as table1's shard sharing the same tile_key range (shardminvalue).
39+
-- Citus' own colocate_with option doesn't support range-distributed tables,
40+
-- so this does the move explicitly with citus_move_shard_placement() instead
41+
-- of relying on Citus' colocation groups.
3842
----------------------------------------------------------------------------------------------------------------------
39-
CREATE OR REPLACE FUNCTION colocate_multirelation(table1 text, table2 text)
43+
DROP FUNCTION IF EXISTS colocate_multirelation;
44+
CREATE OR REPLACE FUNCTION colocate_shards(table1 text, table2 text)
4045
RETURNS boolean
4146
LANGUAGE plpgsql
4247
AS $$
4348
DECLARE
44-
i integer;
45-
shard_info record;
46-
node_info record;
47-
node text;
48-
shardid_test bigint;
49+
tile_pair record;
4950
BEGIN
50-
--Move one of them to a new place because one node contains 10 and the other contains 11
51-
--It is an enterpise feature
52-
--SELECT master_move_shard_placement(102660,'pgxl4', 5432,'pgxl2', 5432);
53-
--For every node, update shards information
54-
FOR node_info in SELECT * FROM master_get_active_worker_nodes()
51+
FOR tile_pair IN
52+
SELECT s2.shardid AS moving_shard,
53+
n1.nodename AS target_node, n1.nodeport AS target_port,
54+
n2.nodename AS source_node, n2.nodeport AS source_port
55+
FROM pg_dist_shard s1
56+
JOIN pg_dist_placement p1 ON p1.shardid = s1.shardid
57+
JOIN pg_dist_node n1 ON n1.groupid = p1.groupid AND n1.noderole = 'primary'
58+
JOIN pg_dist_shard s2 ON s2.shardminvalue = s1.shardminvalue
59+
JOIN pg_dist_placement p2 ON p2.shardid = s2.shardid
60+
JOIN pg_dist_node n2 ON n2.groupid = p2.groupid AND n2.noderole = 'primary'
61+
WHERE s1.logicalrelid = table1::regclass
62+
AND s2.logicalrelid = table2::regclass
63+
AND (n1.nodename, n1.nodeport) IS DISTINCT FROM (n2.nodename, n2.nodeport)
5564
LOOP
56-
i = 0;
57-
--Get the shards of every table in every node
58-
FOR shard_info in SELECT shard.shardid, shard.shardminvalue, shard.shardmaxvalue
59-
FROM pg_dist_placement AS placement, pg_dist_node AS node, pg_dist_shard As shard
60-
WHERE placement.groupid = node.groupid
61-
AND shard.logicalrelid = table1::regclass
62-
AND placement.shardid = shard.shardid
63-
AND node.noderole = 'primary'
64-
AND nodename=node_info.node_name
65-
LOOP
66-
SELECT shard.shardid
67-
FROM pg_dist_placement AS placement, pg_dist_node AS node, pg_dist_shard As shard
68-
WHERE placement.groupid = node.groupid
69-
AND shard.logicalrelid = table2::regclass
70-
AND placement.shardid = shard.shardid
71-
AND node.noderole = 'primary'
72-
AND nodename=node_info.node_name offset i limit 1 INTO shardid_test;
73-
--RAISE NOTICE 'Update:%',shardid_test;
74-
UPDATE pg_dist_shard SET shardminvalue = shard_info.shardminvalue, shardmaxvalue=shard_info.shardmaxvalue
75-
WHERE shardid = shardid_test;
76-
i := i + 1;
77-
END LOOP;
65+
PERFORM citus_move_shard_placement(tile_pair.moving_shard,
66+
tile_pair.source_node, tile_pair.source_port,
67+
tile_pair.target_node, tile_pair.target_port,
68+
'block_writes');
7869
END LOOP;
7970
RETURN TRUE;
8071
END;
@@ -104,15 +95,12 @@ BEGIN
10495
/* create_range_shards() above already assigns reshuffled_table's shards
10596
* the correct tile_key-aligned ranges (1..shards), matching tableName's
10697
* own tile numbering by convention -- both tables tile the same way.
107-
* This used to also call colocate_multirelation() to try to physically
108-
* co-locate the two tables' shard placements onto the same nodes, but
109-
* that function's actual placement-move step is unimplemented (commented
110-
* out) and its remaining code just re-copies shard ranges by pairing
111-
* shards positionally per node -- which silently overwrites the correct
112-
* ranges above with wrong, duplicated ones whenever the two tables'
113-
* shards aren't distributed identically across nodes (the common case).
114-
* Until shard co-location is implemented for real, skip it entirely
115-
* rather than corrupt the ranges that are already correct. */
98+
* Citus' shard placement for the newly-created shards is otherwise
99+
* independent of tableName's placement, so without this, a tile-key
100+
* join between the two tables is a cross-node repartition even though
101+
* both sides cover the same tile_key range. colocate_shards() moves
102+
* each of reshuffled_table's shards onto tableName's matching-tile node. */
103+
PERFORM colocate_shards(tableName, reshuffled_table);
116104
RETURN true;
117105
END;
118106
$$;

src/catalog/nodes.c

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <executor/spi.h>
1818
#include "catalog/nodes.h"
1919
#include <utils/lsyscache.h>
20+
#include <utils/builtins.h>
2021
#include "executor/executor_tasks.h"
2122
#include "catalog/table_ops.h"
2223

@@ -118,6 +119,55 @@ char * GetRandomTileId(Oid relationId, ExecTaskType taskType, int rand_tile)
118119
return NULL;
119120
}
120121

122+
/*
123+
* GetShardHostNode looks up the (nodename, nodeport) actually hosting
124+
* relationId's shard whose shardminvalue matches rand_tile, for dispatching
125+
* a command to the specific worker that holds that tile's data (unlike
126+
* GetNodeInfo(), which just picks a random node from pg_dist_node).
127+
*/
128+
extern TaskNode *
129+
GetShardHostNode(Oid relationId, int rand_tile)
130+
{
131+
TaskNode *taskNode = (TaskNode *) palloc0(sizeof(TaskNode));
132+
int spi_result = SPI_connect();
133+
if (spi_result != SPI_OK_CONNECT)
134+
{
135+
elog(ERROR, "Could not connect to database using SPI");
136+
}
137+
138+
StringInfo logicalrel = makeStringInfo();
139+
if (IsReshuffledTable(relationId))
140+
appendStringInfo(logicalrel, "%s.%s", Var_Schema, get_rel_name(relationId));
141+
else
142+
appendStringInfo(logicalrel, "%s", get_rel_name(relationId));
143+
144+
StringInfo catalogQuery = makeStringInfo();
145+
appendStringInfo(catalogQuery,
146+
"SELECT node.nodename, node.nodeport\n"
147+
"FROM pg_dist_shard shard\n"
148+
"JOIN pg_dist_placement placement ON placement.shardid = shard.shardid\n"
149+
"JOIN pg_dist_node node ON node.groupid = placement.groupid AND node.noderole = 'primary'\n"
150+
"WHERE shard.logicalrelid = '%s'::regclass\n"
151+
" AND shard.shardminvalue = %d::text",
152+
logicalrel->data, rand_tile);
153+
spi_result = SPI_execute(catalogQuery->data, true, 1);
154+
if (spi_result == SPI_OK_SELECT && SPI_processed > 0)
155+
{
156+
bool isNull;
157+
HeapTuple row = SPI_copytuple(SPI_tuptable->vals[0]);
158+
TupleDesc rowDescriptor = SPI_tuptable->tupdesc;
159+
char *nodename = SPI_getvalue(row, rowDescriptor, 1);
160+
taskNode->node = CStringGetTextDatum(nodename);
161+
taskNode->port = DatumGetInt32(SPI_getbinval(row, rowDescriptor, 2, &isNull));
162+
}
163+
spi_result = SPI_finish();
164+
if (spi_result != SPI_OK_FINISH)
165+
{
166+
elog(ERROR, "Could not disconnect from database using SPI");
167+
}
168+
return taskNode;
169+
}
170+
121171
/* GetDBName returns the name of the database the current backend is connected to. */
122172
extern Datum
123173
GetDBName()

src/executor/multi_phase_executor.c

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <catalog/namespace.h>
2222
#include <access/xact.h>
2323
#include "utils/planner_utils.h"
24+
#include "utils/helper_functions.h"
2425
#include "planner/planner_strategies.h"
2526

2627
static void ConstructNeighborScanQuery(Rte *tbl, char * query_string, STMultirelation *base,
@@ -36,6 +37,7 @@ static GeneralScan *ConstructGeneralQuery(DistributedSpatiotemporalQueryPlan *di
3637
MultiPhaseExecutor *multiPhaseExecutor);
3738
static void IndexReshuffledData(Rte *reshuffledTable, MultiPhaseExecutor *multiPhaseExecutor);
3839
static void ConstructPostProcessingPhase(CoordinatorLevelOperator *coordOp, MultiPhaseExecutor *multiPhaseExecutor);
40+
static char *EliminateShapeSegmentDuplicates(char *query_string, bool hasDistributedAggregate);
3941

4042

4143

@@ -137,9 +139,12 @@ ColocateRte(STMultirelation *base, Rte *other)
137139
char *reshuffled_table = get_rel_name(cell->relid);
138140
Var *distributionColumn = DistPartitionKey(base->catalogTableInfo.table_oid);
139141
int shardCount = ShardIntervalCount(base->catalogTableInfo.table_oid);
140-
/* Citus' CreateDistributedTable() expects the literal string "default"
141-
* (not NULL) to mean "no explicit colocation group" -- IsColocateWithDefault()
142-
* dereferences it directly and crashes on NULL. */
142+
/* Citus rejects colocate_with for range-distributed tables
143+
* ("colocate_with option is not supported for append / range
144+
* distributed tables"), so this stays "default"; physical
145+
* co-location with base is instead done after the fact by
146+
* colocate_shards() in create_reshuffled_multirelation, which
147+
* explicitly moves each shard onto base's matching-tile node. */
143148
char *parentRelationName = "default";
144149

145150
DropReshuffledTableIfExists(citusRteNode->reshuffledTable);
@@ -182,9 +187,12 @@ createReshuffledTable(STMultirelation *base, STMultirelation *other)
182187
char *reshuffled_table = get_rel_name(other->catalogTableInfo.table_oid);
183188
Var *distributionColumn = DistPartitionKey(other->catalogTableInfo.table_oid);
184189
int shardCount = ShardIntervalCount(base->catalogTableInfo.table_oid);
185-
/* Citus' CreateDistributedTable() expects the literal string "default"
186-
* (not NULL) to mean "no explicit colocation group" -- IsColocateWithDefault()
187-
* dereferences it directly and crashes on NULL. */
190+
/* Citus rejects colocate_with for range-distributed tables
191+
* ("colocate_with option is not supported for append / range
192+
* distributed tables"), so this stays "default"; physical
193+
* co-location with base is instead done after the fact by
194+
* colocate_shards() in create_reshuffled_multirelation, which
195+
* explicitly moves each shard onto base's matching-tile node. */
188196
char *parentRelationName = "default";
189197

190198
DropReshuffledTableIfExists(other->catalogTableInfo.reshuffledTable);
@@ -402,6 +410,28 @@ IndexReshuffledData(Rte *reshuffledTable, MultiPhaseExecutor *multiPhaseExecutor
402410

403411
}
404412

413+
/*
414+
* EliminateShapeSegmentDuplicates removes shape-segmentation duplicates from
415+
* query_string by wrapping it as `SELECT DISTINCT * FROM (query_string) AS
416+
* x`, deduping on whatever the query actually projects. Only applied when
417+
* no distributed aggregate is involved (see hasDistributedAggregate at the
418+
* call site) -- for an aggregate like count(*), the duplicates are already
419+
* consumed before this outer DISTINCT would ever see them, so this is left
420+
* as a no-op (returns NULL) for that case rather than risking a rewrite
421+
* Citus' planner may reject for non-colocated repartition joins.
422+
*/
423+
static char *
424+
EliminateShapeSegmentDuplicates(char *query_string, bool hasDistributedAggregate)
425+
{
426+
if (hasDistributedAggregate)
427+
return NULL;
428+
429+
char *innerQuery = replaceWord(query_string, ";", " ");
430+
StringInfo dedupedQuery = makeStringInfo();
431+
appendStringInfo(dedupedQuery, "SELECT DISTINCT * FROM (%s) AS dedup_result", innerQuery);
432+
return dedupedQuery->data;
433+
}
434+
405435
/*
406436
* ConstructGeneralQuery assembles the final SQL text to execute: it unions
407437
* together the worker-phase task query for each strategy used in the plan
@@ -449,6 +479,29 @@ ConstructGeneralQuery(DistributedSpatiotemporalQueryPlan *distPlan, MultiPhaseEx
449479
else
450480
elog(ERROR, "The query executor could not identify the planner strategy");
451481
}
482+
/* NonColocation/Colocation strategies reshuffle onto tiles built from a
483+
* spatiotemporal shape, which can place the same row's shape-segmented
484+
* copy in more than one tile so a boundary-crossing match isn't missed
485+
* -- see checkQueryType's dupRemOperator->active assignment. That makes
486+
* the worker-phase join above contain the same logical match
487+
* (base_row1, base_row2) more than once. This has to be resolved here,
488+
* on the flat two-table query before the coordinator/aggregate-rewriter
489+
* wrapping below nests it inside a "select sum(...) from (...) as fQ"
490+
* subquery -- once that wrapping happens, ExtractRangeTableEntryList
491+
* sees three range table entries (the subquery plus its two inner
492+
* tables) instead of the two this rewrite expects, and any duplicate
493+
* rows have already been consumed by the inner aggregate anyway. */
494+
if (distPlan->postProcessing->coordinatorLevelOperator->dupRemOperator->active)
495+
{
496+
bool hasDistributedAggregate = list_length(distPlan->postProcessing->distfuns) > 0;
497+
char *deduped = EliminateShapeSegmentDuplicates(generalScan->query_string->data,
498+
hasDistributedAggregate);
499+
if (deduped != NULL)
500+
{
501+
resetStringInfo(generalScan->query_string);
502+
appendStringInfo(generalScan->query_string, "%s", deduped);
503+
}
504+
}
452505
/* Loop though the post processing tasks */
453506
if (generalScan->length == 0)
454507
appendStringInfo(generalScan->query_string,"%s",

0 commit comments

Comments
 (0)