Skip to content

Commit 7d0d37f

Browse files
authored
Merge pull request #25 from mbakli/worker_level_optimization
Worker level optimization
2 parents 009a853 + a9dbf3f commit 7d0d37f

22 files changed

Lines changed: 2116 additions & 67 deletions

include/catalog/nodes.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ extern TaskNode *GetShardHostNode(Oid relationId, int rand_tile);
2929
/* Table id of the tile assigned to a randomly-picked worker for relationId. */
3030
extern char* GetRandomTileId(Oid relationId, ExecTaskType taskType, int rand_tile);
3131

32+
/* Physical shard name for a Citus reference table's single (every-node-replicated) shard. */
33+
extern char* GetReferenceTableShardName(Oid relationId);
34+
3235
/* Looks up the tiling method used to distribute relationId; -1 if not distributed. */
3336
extern int TilingSearch(Oid relationId);
3437

include/distributed_functions/distributed_function.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ extern DistributedFunction *addDistributedFunction(TargetEntry *operation);
6161
/* True if targetEntry's expression is a registered distributable function. */
6262
extern bool IsDistFunc(TargetEntry *targetEntry);
6363

64+
/* Looks up workerFuncName's registered "final" combining op (e.g. "sum" for "length"), or NULL if unregistered. */
65+
extern char *LookupDistFuncFinalOp(const char *workerFuncName);
66+
67+
/* Looks up workerFuncName's registered "combiner" op, or NULL if it has none (the common case today). */
68+
extern char *LookupDistFuncCombinerOp(const char *workerFuncName);
69+
6470
/* Builds a QOperation pairing a distributed op (des) with its argument column (cur). */
6571
extern QOperation * AddQOperation(Datum des, Datum cur);
6672
#endif /* DISTRIBUTED_FUNCTION_H */

include/planner/distributed_mobilitydb_planner.h

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,27 @@ typedef struct DistributedSpatiotemporalQueryPlan
6262
char *org_query_string;
6363
Datum range_bbox;
6464
PostProcessing *postProcessing;
65+
/*
66+
* Set when RewriteSegmentedDistFuncCalls rewrote the query (bare
67+
* distributed-function call over a segmented table -> explicit grouped
68+
* aggregate) and handed off to Citus' own planner directly. The
69+
* EXPLAIN hook treats a non-NULL PlannedStmt from
70+
* distributed_mobilitydb_planner_internal as "our custom planning
71+
* bailed out, re-explain the original query" -- which is wrong here,
72+
* since this *is* the plan that actually runs; without this, EXPLAIN
73+
* would show the original bare (ungrouped) query shape instead of what
74+
* was really executed.
75+
*/
76+
char *segmentedRewriteQuery;
77+
/*
78+
* Set alongside segmentedRewriteQuery: one line per rewritten function
79+
* naming its registered worker/combiner/final ops (from
80+
* pg_dist_spatiotemporal_dist_functions) and the op actually applied in
81+
* the rewrite -- for EXPLAIN to show *why* the rewrite looks the way it
82+
* does in this extension's own worker/combiner/final vocabulary,
83+
* without asserting anything about replication vs. true segmentation.
84+
*/
85+
char *segmentedRewriteExplainNotes;
6586
} DistributedSpatiotemporalQueryPlan;
6687

6788
/* Filter Operation */

include/planner/query_semantics.h

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,35 @@
2020
/* Scans the SELECT targetlist for distributed aggregates and records them into postProcessing. */
2121
extern void analyseSelectClause(List *targetList, PostProcessing *postProcessing);
2222

23+
/*
24+
* Rewrites a bare distributed-function call over a segmented table into a
25+
* grouped-by-trip aggregate query; NULL if nothing to rewrite. On success,
26+
* *explainNotesOut is set to a human-readable, newline-joined description
27+
* of each rewritten function's worker/combiner/final ops and the op
28+
* actually applied (for EXPLAIN); left untouched on a NULL return.
29+
*/
30+
extern char *RewriteSegmentedDistFuncCalls(Query *parse, const char *query_string, STMultirelations *tablesList,
31+
char **explainNotesOut);
32+
33+
/*
34+
* Rewrites a query with an explicit aggregate over a registered distributed
35+
* function applied to a replicated (isMobilityDB, segmented) table's column
36+
* -- e.g. `SUM(length(atTime(t.Trip, p.Period))) ... GROUP BY ...` -- into a
37+
* two-level dedupe/aggregate query, so a trip replicated across N tiles
38+
* contributes to the aggregate once instead of N times. NULL if nothing to
39+
* rewrite.
40+
*/
41+
extern char *RewriteReplicatedAggregateQuery(Query *parse, const char *query_string, STMultirelations *tablesList);
42+
43+
/*
44+
* Same problem as RewriteReplicatedAggregateQuery, but for a query whose
45+
* aggregate-over-distributed-function lives inside one of its own CTEs
46+
* (e.g. `WITH x AS (SELECT ... SUM(length(atTime(...))) ... GROUP BY ...)
47+
* SELECT ... FROM x`) rather than at the top level; NULL if no CTE needed
48+
* rewriting (or there are no CTEs at all).
49+
*/
50+
extern char *RewriteReplicatedAggregateInCTEs(Query *parse, const char *query_string, STMultirelations *tablesList);
51+
2352
/* Analyses fromExpr's predicates against tbl's catalog to derive its candidate-tile filter. */
2453
extern CatalogFilter *AnalyseCatalog(STMultirelation *tbl, FromExpr * fromExpr);
2554

include/utils/helper_functions.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,22 @@ extern char *change_sentence (char *sentence, char *find, char *replace);
3636
/* Returns a lowercased copy of str. */
3737
extern char *toLower(char *str);
3838

39+
/*
40+
* Finds the first case-insensitive, whitespace-bounded occurrence of
41+
* `keyword` in `lowered` (already-lowercased haystack, already-lowercase
42+
* keyword) -- tolerates newlines/tabs/multiple spaces around it, unlike a
43+
* plain strstr(haystack, " keyword "). Returns a pointer to the start of
44+
* the keyword itself, or NULL if not found.
45+
*/
46+
extern char *FindKeywordToken(const char *lowered, const char *keyword);
47+
48+
/*
49+
* Like FindKeywordToken, but only matches an occurrence at paren-depth 0
50+
* (not nested inside a subquery/CTE's own parenthesized definition) -- for
51+
* locating a keyword that belongs to a whole query's outermost SELECT.
52+
*/
53+
extern char *FindTopLevelKeywordToken(const char *lowered, const char *keyword);
54+
3955
/* True if val is a NULL/zero Datum (no value set). */
4056
extern bool IsDatumEmpty(Datum val);
4157

include/utils/planner_utils.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929

3030

3131
/* constants for tiles.options */
32-
#define Natts_MTS 14
32+
#define Natts_MTS 15
3333
#define Anum_MTS_oid 1
3434
#define Anum_MTS_numTiles 3
3535
#define Anum_MTS_method 4
@@ -42,7 +42,14 @@
4242
#define Anum_MTS_tileKey 11
4343
#define Anum_MTS_segmentation 12
4444
#define Anum_MTS_srid 13
45-
#define Anum_MTS_groupCol 5
45+
/* groupcol was appended as the table's 15th (0-indexed 14th) column --
46+
* it did not exist when this catalog table was first designed, so unlike
47+
* the constants above (which match physical column order), this one can't
48+
* be slotted in without renumbering every constant after it. Previously
49+
* defined as 5, colliding with Anum_MTS_type -- vestigial from a groupCol
50+
* column that was never actually added to the table, so this was always
51+
* dead/wrong (GetTilingSchemeInfo never read it). */
52+
#define Anum_MTS_groupCol 14
4653

4754
/* MobilityDB and PostGIS variables */
4855
#define Var_MobilityDB_BBOX "mobdb_bbox"

sql/distributed_mobilitydb--1.0.sql

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ CREATE TABLE dist_mobilitydb.pg_dist_spatiotemporal_tables(
6161
distcoltype varchar(10),
6262
tilekey varchar(10),
6363
shapeSegmented boolean,
64-
srid int
64+
srid int,
65+
groupcol varchar(50)
6566
);
6667

6768
ALTER TABLE dist_mobilitydb.pg_dist_spatiotemporal_tables
@@ -91,14 +92,36 @@ CREATE TABLE dist_mobilitydb.pg_spatiotemporal_join_operations(
9192
);
9293

9394
-- Add the OID for the distance and intersection query operations
95+
--
96+
-- GetPredicateOidAndArgs (src/planner/predicate_management.c) reads an
97+
-- OpExpr predicate's *operator* oid (opExpr->opno), not the oid of the
98+
-- function implementing it -- registering only pg_proc.oid values (as the
99+
-- two SELECTs below do) means an operator-written predicate like
100+
-- `t.Trip && p.Period` (the `&&` bbox/temporal-overlap operator,
101+
-- implemented by temporal_overlaps/span_overlaps/etc., but a *different*
102+
-- oid than those functions') never matched, even after the function
103+
-- itself was registered. For queries that use `&&` as their *only*
104+
-- spatiotemporal predicate (no accompanying eintersects/ST_Intersects/
105+
-- eDwithin, e.g. BerlinMOD Q8's "was this vehicle active during this
106+
-- period" check), that meant this extension's planner never engaged at
107+
-- all -- no tile pruning, none of its tile-boundary deduplication --
108+
-- reproduced as genuinely wrong (duplicated) results. The third SELECT
109+
-- below registers the `&&` *operator*'s own oid (from pg_operator) for
110+
-- every temporal/spatiotemporal type combination it's defined over.
94111
INSERT INTO dist_mobilitydb.pg_spatiotemporal_join_operations(op, opid, distance)
95112
SELECT proname,oid,true
96113
FROM pg_proc
97114
WHERE proname like ANY(ARRAY['%dwithin%', '%distance%'])
98115
union all
99116
SELECT proname,oid,false
100117
FROM pg_proc
101-
WHERE proname like ANY(ARRAY['%intersects%', '%contains%', '%disjoint%']);
118+
WHERE proname like ANY(ARRAY['%intersects%', '%contains%', '%disjoint%', '%overlaps%'])
119+
union all
120+
SELECT '&&', o.oid, false
121+
FROM pg_operator o
122+
WHERE o.oprname = '&&'
123+
AND (o.oprleft::regtype::text ~ 'tgeompoint|tgeogpoint|tbool|tint|tfloat|ttext|tnpoint|tstz'
124+
OR o.oprright::regtype::text ~ 'tgeompoint|tgeogpoint|tbool|tint|tfloat|ttext|tnpoint|tstz');
102125

103126
ALTER TABLE dist_mobilitydb.pg_spatiotemporal_join_operations
104127
SET SCHEMA pg_catalog;

sql/helper_functions/write_to_catalog.sql

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ BEGIN
1212
column_type := 'geometry';
1313
END IF;
1414
EXECUTE format('%s', concat('' ||
15-
'INSERT INTO pg_dist_spatiotemporal_tables (tblOid, tableName, numTiles, tilingMethod, tilingType, granularity, disjoint, isMobilityDB, distcol, distcoltype, tilekey, shapeSegmented, srid) ' ||
16-
'VALUES (',0,',''',table_name_out,''',',tiling.numTiles,',''',tiling.method,''',''',tiling.type,''',''',tiling.granularity,''',''',tiling.disjointTiles,''',''',tiling.isMobilityDB,''', ''',tiling.distCol,''', ''',tiling.distColType,''', ''',tiling.tileKey,''', ''',tiling.segmentation,''', ',tiling.srid,') ' ||
15+
'INSERT INTO pg_dist_spatiotemporal_tables (tblOid, tableName, numTiles, tilingMethod, tilingType, granularity, disjoint, isMobilityDB, distcol, distcoltype, tilekey, shapeSegmented, srid, groupcol) ' ||
16+
'VALUES (',0,',''',table_name_out,''',',tiling.numTiles,',''',tiling.method,''',''',tiling.type,''',''',tiling.granularity,''',''',tiling.disjointTiles,''',''',tiling.isMobilityDB,''', ''',tiling.distCol,''', ''',tiling.distColType,''', ''',tiling.tileKey,''', ''',tiling.segmentation,''', ',tiling.srid,', ''',tiling.groupCol,''') ' ||
1717
'ON CONFLICT (tableName) ' ||
1818
'DO ' ||
1919
'UPDATE set tableName = EXCLUDED.tableName,' ||
@@ -27,7 +27,8 @@ BEGIN
2727
'distcoltype = EXCLUDED.distcoltype,' ||
2828
'tilekey = EXCLUDED.tilekey,' ||
2929
'shapeSegmented = EXCLUDED.shapeSegmented,' ||
30-
'srid = EXCLUDED.srid' ));
30+
'srid = EXCLUDED.srid,' ||
31+
'groupcol = EXCLUDED.groupcol' ));
3132

3233
EXECUTE format('%s', concat('SELECT id FROM pg_dist_spatiotemporal_tables WHERE tableName = ''',table_name_out,''''))
3334
INTO table_out_id;

sql/partitioning/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ SET(LOCAL_FILES
33
shape_segmentation.sql
44
search_in_dimensions.sql
55
crange.sql
6+
hierarchical.sql
67
colocation.sql
78
tiling.sql
89
data_allocation.sql

sql/partitioning/data_allocation.sql

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ BEGIN
2525
IF not tiling.isMobilityDB and tiling.internaltype = 'linestring' THEN
2626
EXECUTE format('%s', concat('ALTER TABLE ', table_name_out,' ALTER COLUMN ', tiling.distCol,' TYPE geometry;'));
2727
END IF;
28+
/*
29+
* The distributed column holds large TOASTed values (whole/clipped
30+
* trajectories for MobilityDB, geometries for PostGIS) -- switching its
31+
* TOAST compression from the default pglz to lz4 (much faster to
32+
* compress, at a modest space cost) cuts the segmentation/allocation
33+
* INSERT's dominant cost, which is writing these rows, not scanning
34+
* them (confirmed via EXPLAIN ANALYZE: the write phase alone accounts
35+
* for the majority of this step's time, and that write can't be sped
36+
* up by parallel workers -- Postgres disables parallel query entirely
37+
* for any statement containing a write, regardless of GUCs). Measured
38+
* ~2.75x faster (58.8s -> 21.4s) on a same-data before/after comparison
39+
* of this exact INSERT. No effect on query results, only on-disk
40+
* compression of this one column.
41+
*/
42+
EXECUTE format('%s', concat('ALTER TABLE ', table_name_out,' ALTER COLUMN ', tiling.distCol,' SET COMPRESSION lz4;'));
2843
-- Add the distributed column
2944
EXECUTE format('%s', concat('ALTER TABLE ',table_name_out, ' ADD column ',tiling.tileKey,' integer'));
3045
-- Distribute the table using range multirelation
@@ -114,6 +129,24 @@ BEGIN
114129
SELECT ',org_table_columns, ',',tiling.tileKey,'
115130
FROM ',table_name_in,' t1, pg_dist_spatiotemporal_tiles
116131
WHERE table_id=',table_id,' and ', tiling.distCol,' && ',bbox_with_srid));
132+
ELSIF tiling.internaltype in ('sequence','sequenceset') THEN
133+
/*
134+
* Mirrors the linestring/polygon branch above: replicate each trip
135+
* whole (unclipped) into every tile it overlaps, rather than
136+
* segmenting/clipping it (see segmentation_and_allocation for the
137+
* clipping counterpart, selected instead whenever
138+
* tiling.segmentation is true). Previously missing entirely --
139+
* shape_segmentation => false (the "replicate" choice) fell into
140+
* the catch-all ELSE below and errored out for any MobilityDB
141+
* sequence/sequenceset table (e.g. BerlinMOD trips), even though
142+
* the segmenting path already worked.
143+
*/
144+
RAISE INFO 'Distributing the %s into the overlapping tiles without segmenting (i.e., replication) them:',tiling.internaltype;
145+
EXECUTE format('%s', concat('
146+
INSERT INTO ',table_name_out,'
147+
SELECT ',org_table_columns, ',',tiling.tileKey,'
148+
FROM ',table_name_in,' t1, pg_dist_spatiotemporal_tiles
149+
WHERE table_id=',table_id,' and ', tiling.distCol,' && ',bbox_with_srid));
117150
ELSE
118151
RAISE Exception 'The column type is not detected!';
119152
END IF;

0 commit comments

Comments
 (0)