Skip to content

Commit 40db431

Browse files
committed
Show details Citus query plan instead of just 'Custom Scan'
1 parent 91ad54d commit 40db431

File tree

6 files changed

+211
-0
lines changed

6 files changed

+211
-0
lines changed
Lines changed: 42 additions & 0 deletions
Loading
Lines changed: 24 additions & 0 deletions
Loading
Lines changed: 16 additions & 0 deletions
Loading
Lines changed: 14 additions & 0 deletions
Loading

web/pgadmin/static/js/Explain/ImageMapper.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,85 @@ const ImageMapper = {
4141
'image': 'ex_bmp_or.svg',
4242
'image_text': 'Bitmap OR',
4343
},
44+
'Citus Job': function(data) {
45+
// A 'Citus Job' represents a distributed query operation.
46+
// The details of the distributed operation are in the sub-plans,
47+
// but this node contains task count information, showing how many shards
48+
// the query is being distributed to.
49+
50+
const taskCount = data['Task Count'];
51+
const tasksShown = data['Tasks Shown'];
52+
53+
// "Task Count" is the number of shard operations being run.
54+
// "Tasks Shown" is either "All" or "One of N" depending on whether the returned query plan
55+
// contains one sample task or all of them.
56+
57+
// We show single-shard or multi-shard with different images, and we show the
58+
// literal value of 'Tasks Shown' as the image text.
59+
60+
const image = (taskCount === 1)
61+
? 'ex_citus_distributed_one_of_one.svg'
62+
: 'ex_citus_distributed_one_of_many.svg';
63+
64+
return {
65+
'image': image,
66+
'image_text': tasksShown
67+
};
68+
},
69+
'Citus Task': function(data) {
70+
// A 'Citus Task' represents a Task executed on a particular worker node.
71+
// The details of the Task are in the sub-plans, so for this node we just show
72+
// some details of the worker node.
73+
74+
const node = data['Node'];
75+
// "Node" has a value like "host=citus-worker-7 port=8394 dbname=postgres"
76+
// That's a bit long to display, so we shrink it to 'citus-worker-7:8394 postgres'
77+
const hostMatch = node.match(/host=(\S+)/);
78+
const portMatch = node.match(/port=(\S+)/);
79+
const dbnameMatch = node.match(/dbname=(\S+)/);
80+
81+
const host = hostMatch[1] || '';
82+
let port = portMatch[1] || '';
83+
if (port === '5432') {
84+
// Default port. Don't bother showing.
85+
port = '';
86+
}
87+
const dbname = dbnameMatch[1] || '';
88+
let text = `Task ${host}`;
89+
if (port) {
90+
text += `:${port}`;
91+
}
92+
if (dbname) {
93+
text += ` ${dbname}`;
94+
}
95+
return {
96+
'image': 'ex_citus_worker_task.svg',
97+
'image_text': text
98+
};
99+
},
44100
'CTE Scan': {
45101
'image': 'ex_cte_scan.svg',
46102
'image_text': 'CTE Scan',
47103
},
104+
'Custom Scan': function(data) {
105+
const customPlanProvider = data['Custom Plan Provider'];
106+
107+
let image;
108+
109+
switch (customPlanProvider) {
110+
case 'Citus Adaptive':
111+
image = 'ex_citus.svg';
112+
break;
113+
default:
114+
image = 'ex_unknown.svg';
115+
break;
116+
}
117+
118+
return {
119+
'image': image,
120+
'image_text': data['Custom Plan Provider']
121+
};
122+
},
48123
'Function Scan': {
49124
'image': 'ex_result.svg',
50125
'image_text': 'Function Scan',

web/pgadmin/static/js/Explain/index.jsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,46 @@ function parsePlan(data, ctx) {
350350
}
351351
}
352352

353+
const citusDistributedQuery = data['Distributed Query'];
354+
if (citusDistributedQuery) {
355+
// This is a Citus Distributed Query plan.
356+
// It contains a 'Job' with one or more 'Tasks' in it.
357+
// We'll convert those Tasks into sub-Plans of this main plan and process it
358+
// with the regular Plan layout code.
359+
delete data['Distributed Query'];
360+
361+
// Convert the Job into a 'Citus Job' sub-plan.
362+
// That allows us to show details of the Task count etc.
363+
const citusJob = citusDistributedQuery['Job'];
364+
const jobPlan = {
365+
'Node Type': 'Citus Job',
366+
...citusJob
367+
};
368+
data['Plans'] = [jobPlan];
369+
370+
// Convert each of the Tasks into 'Citus Task' sub-plans of the Job plan.
371+
const citusTasks = jobPlan['Tasks'];
372+
if (citusTasks) {
373+
delete jobPlan['Tasks'];
374+
375+
const citusTaskPlans = citusTasks.map(citusJobTask => {
376+
const taskPlan = {
377+
'Node Type': 'Citus Task',
378+
...citusJobTask
379+
};
380+
381+
// A Citus Task contains a 'Remote Plan' which is the actual plan
382+
// executed on the worker nodes. It's actually an array of arrays.
383+
const remotePlan = taskPlan['Remote Plan'];
384+
delete taskPlan['Remote Plan'];
385+
// A Remote Plan is an array of arrays of Plans.
386+
taskPlan['Plans'] = remotePlan.flatMap(arr => arr.map(planLevel1Entry => planLevel1Entry['Plan']));
387+
return taskPlan;
388+
});
389+
jobPlan['Plans'] = citusTaskPlans;
390+
}
391+
}
392+
353393
// Start calculating xpos, ypos, width and height for child plans if any
354394
if ('Plans' in data) {
355395
data['width'] += offsetX;

0 commit comments

Comments
 (0)