| #include "postgres.h" |
| |
| #include "access/relscan.h" |
| #include "access/tableam.h" |
| #include "catalog/pg_type.h" |
| #include "executor/executor.h" |
| #include "fmgr.h" |
| #include "funcapi.h" |
| #include "miscadmin.h" |
| #include "nodes/extensible.h" |
| #include "nodes/makefuncs.h" |
| #include "optimizer/optimizer.h" |
| #include "cdb/cdbpathlocus.h" |
| #include "optimizer/pathnode.h" |
| #include "optimizer/paths.h" |
| #include "optimizer/restrictinfo.h" |
| #include "port/atomics.h" |
| #include "storage/shm_toc.h" |
| #include "utils/builtins.h" |
| #include "utils/guc.h" |
| #include "utils/rel.h" |
| |
| PG_MODULE_MAGIC; |
| |
| void _PG_init(void); |
| void _PG_fini(void); |
| |
| PG_FUNCTION_INFO_V1(pcs_get_hook_calls); |
| |
| /* GUC */ |
| static bool pcs_enabled = false; |
| |
| /* |
| * Per-process hook-call counters. Reset in EstimateDSMCustomScan (which is |
| * called once per parallel-mode invocation in the leader). The cross-worker |
| * InitializeWorkerCustomScan count is aggregated into the DSM atomic and |
| * harvested back into the leader-local counter in ShutdownCustomScan. |
| */ |
| static int64 pcs_n_estimate = 0; |
| static int64 pcs_n_init_dsm = 0; |
| static int64 pcs_n_reinit_dsm = 0; |
| static int64 pcs_n_init_worker = 0; |
| static int64 pcs_n_shutdown = 0; |
| |
| /* |
| * DSM header. The child SeqScan owns the parallel table scan descriptor, so |
| * the wrapper only needs a tiny shared area to count InitializeWorker calls |
| * across workers. |
| */ |
| typedef struct PcsDSM |
| { |
| pg_atomic_uint32 init_worker_calls; |
| } PcsDSM; |
| |
| typedef struct PcsState |
| { |
| CustomScanState csstate; |
| PcsDSM *dsm; /* set in InitDSM / InitWorker */ |
| } PcsState; |
| |
| static set_rel_pathlist_hook_type prev_pathlist_hook = NULL; |
| |
| static Plan *pcs_plan_path(PlannerInfo *root, RelOptInfo *rel, |
| CustomPath *best_path, List *tlist, |
| List *clauses, List *custom_plans); |
| static Node *pcs_create_state(CustomScan *cscan); |
| static void pcs_begin(CustomScanState *node, EState *estate, int eflags); |
| static TupleTableSlot *pcs_exec(CustomScanState *node); |
| static void pcs_end(CustomScanState *node); |
| static void pcs_rescan(CustomScanState *node); |
| static Size pcs_estimate_dsm(CustomScanState *node, ParallelContext *pcxt); |
| static void pcs_init_dsm(CustomScanState *node, ParallelContext *pcxt, |
| void *coord); |
| static void pcs_reinit_dsm(CustomScanState *node, ParallelContext *pcxt, |
| void *coord); |
| static void pcs_init_worker(CustomScanState *node, shm_toc *toc, void *coord); |
| static void pcs_shutdown(CustomScanState *node); |
| |
| static const CustomPathMethods pcs_path_methods = |
| { |
| .CustomName = "ParallelCustomScan", |
| .PlanCustomPath = pcs_plan_path, |
| }; |
| |
| static const CustomScanMethods pcs_scan_methods = |
| { |
| .CustomName = "ParallelCustomScan", |
| .CreateCustomScanState = pcs_create_state, |
| }; |
| |
| static const CustomExecMethods pcs_exec_methods = |
| { |
| .CustomName = "ParallelCustomScan", |
| .BeginCustomScan = pcs_begin, |
| .ExecCustomScan = pcs_exec, |
| .EndCustomScan = pcs_end, |
| .ReScanCustomScan = pcs_rescan, |
| .EstimateDSMCustomScan = pcs_estimate_dsm, |
| .InitializeDSMCustomScan = pcs_init_dsm, |
| .ReInitializeDSMCustomScan = pcs_reinit_dsm, |
| .InitializeWorkerCustomScan = pcs_init_worker, |
| .ShutdownCustomScan = pcs_shutdown, |
| }; |
| |
| static Plan * |
| pcs_plan_path(PlannerInfo *root, RelOptInfo *rel, CustomPath *best_path, |
| List *tlist, List *clauses, List *custom_plans) |
| { |
| CustomScan *cs = makeNode(CustomScan); |
| |
| cs->scan.plan.targetlist = tlist; |
| /* |
| * The child SeqScan does the filtering (create_scan_plan attaches the |
| * base restrictions to it), so the wrapper itself carries no qual. |
| */ |
| cs->scan.plan.qual = NIL; |
| cs->scan.plan.parallel_aware = best_path->path.parallel_aware; |
| cs->scan.plan.parallel_safe = best_path->path.parallel_safe; |
| /* |
| * The wrapper delegates scanning to its child, so it is not itself a |
| * base-relation scan: scanrelid = 0. custom_scan_tlist must then |
| * describe the scan tuple we hand upward; we mirror the child's output |
| * targetlist, and set_customscan_references() rewrites our own |
| * targetlist to reference it via INDEX_VAR. This keeps projection |
| * correct for multi-column relations -- e.g. the catalog scans |
| * (pg_class) that ANALYZE issues internally, which a fixed |
| * base-relation descriptor would mis-deform. |
| */ |
| cs->scan.scanrelid = 0; |
| cs->flags = best_path->flags; |
| cs->custom_plans = custom_plans; |
| cs->custom_exprs = NIL; |
| cs->custom_private = NIL; |
| cs->custom_scan_tlist = |
| copyObject(((Plan *) linitial(custom_plans))->targetlist); |
| cs->methods = &pcs_scan_methods; |
| |
| return (Plan *) cs; |
| } |
| |
| static Node * |
| pcs_create_state(CustomScan *cscan) |
| { |
| PcsState *st = (PcsState *) newNode(sizeof(PcsState), T_CustomScanState); |
| |
| st->csstate.methods = &pcs_exec_methods; |
| return (Node *) st; |
| } |
| |
| static void |
| pcs_begin(CustomScanState *node, EState *estate, int eflags) |
| { |
| CustomScan *cscan = (CustomScan *) node->ss.ps.plan; |
| Plan *childplan = (Plan *) linitial(cscan->custom_plans); |
| |
| /* |
| * ExecInitCustomScan has already opened the scan relation and set up our |
| * scan/result slots and projection. All we add is the child plan state, |
| * which becomes our sole custom_ps entry. That child performs the actual |
| * (parallel) heap scan and is the node the MPP planstate walkers recurse |
| * into via the T_CustomScanState arm. |
| */ |
| node->custom_ps = list_make1(ExecInitNode(childplan, estate, eflags)); |
| } |
| |
| static TupleTableSlot * |
| pcs_child_next(CustomScanState *node) |
| { |
| PlanState *child = (PlanState *) linitial(node->custom_ps); |
| TupleTableSlot *childslot = ExecProcNode(child); |
| |
| if (TupIsNull(childslot)) |
| return NULL; |
| |
| /* |
| * Move the child's tuple into our scan slot. That slot's descriptor was |
| * built from custom_scan_tlist, which is an exact copy of the child's |
| * output targetlist, so the columns line up positionally and ExecScan's |
| * projection (compiled against the same slot) reads correct values. |
| */ |
| ExecCopySlot(node->ss.ss_ScanTupleSlot, childslot); |
| return node->ss.ss_ScanTupleSlot; |
| } |
| |
| static bool |
| pcs_recheck(CustomScanState *node, TupleTableSlot *slot) |
| { |
| return true; |
| } |
| |
| static TupleTableSlot * |
| pcs_exec(CustomScanState *node) |
| { |
| return ExecScan(&node->ss, |
| (ExecScanAccessMtd) pcs_child_next, |
| (ExecScanRecheckMtd) pcs_recheck); |
| } |
| |
| static void |
| pcs_end(CustomScanState *node) |
| { |
| ExecEndNode((PlanState *) linitial(node->custom_ps)); |
| } |
| |
| static void |
| pcs_rescan(CustomScanState *node) |
| { |
| ExecReScan((PlanState *) linitial(node->custom_ps)); |
| } |
| |
| static Size |
| pcs_estimate_dsm(CustomScanState *node, ParallelContext *pcxt) |
| { |
| /* Reset per-process counters at the start of a parallel run. */ |
| pcs_n_estimate++; |
| pcs_n_init_dsm = 0; |
| pcs_n_reinit_dsm = 0; |
| pcs_n_init_worker = 0; |
| pcs_n_shutdown = 0; |
| |
| return sizeof(PcsDSM); |
| } |
| |
| static void |
| pcs_init_dsm(CustomScanState *node, ParallelContext *pcxt, void *coord) |
| { |
| PcsState *st = (PcsState *) node; |
| PcsDSM *dsm = (PcsDSM *) coord; |
| |
| pg_atomic_init_u32(&dsm->init_worker_calls, 0); |
| st->dsm = dsm; |
| pcs_n_init_dsm++; |
| } |
| |
| static void |
| pcs_reinit_dsm(CustomScanState *node, ParallelContext *pcxt, void *coord) |
| { |
| PcsState *st = (PcsState *) node; |
| PcsDSM *dsm = (PcsDSM *) coord; |
| |
| pg_atomic_write_u32(&dsm->init_worker_calls, 0); |
| st->dsm = dsm; |
| pcs_n_reinit_dsm++; |
| } |
| |
| static void |
| pcs_init_worker(CustomScanState *node, shm_toc *toc, void *coord) |
| { |
| PcsState *st = (PcsState *) node; |
| PcsDSM *dsm = (PcsDSM *) coord; |
| |
| st->dsm = dsm; |
| |
| /* This runs in a worker; the leader reads the aggregate in Shutdown. */ |
| pg_atomic_fetch_add_u32(&dsm->init_worker_calls, 1); |
| } |
| |
| static void |
| pcs_shutdown(CustomScanState *node) |
| { |
| PcsState *st = (PcsState *) node; |
| |
| if (st->dsm != NULL) |
| pcs_n_init_worker = pg_atomic_read_u32(&st->dsm->init_worker_calls); |
| pcs_n_shutdown++; |
| } |
| |
| static void |
| pcs_set_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, Index rti, |
| RangeTblEntry *rte) |
| { |
| CustomPath *cp; |
| CustomPath *pp; |
| double rows; |
| CdbPathLocus inherited_locus; |
| |
| if (prev_pathlist_hook) |
| prev_pathlist_hook(root, rel, rti, rte); |
| |
| if (!pcs_enabled) |
| return; |
| if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION) |
| return; |
| |
| /* |
| * In Cloudberry every Path must carry a CdbPathLocus. Inherit it from |
| * the first existing pathlist entry (set up by set_plain_rel_pathlist) |
| * so the planner can dispatch our CustomScan the same way a regular |
| * SeqScan would be dispatched. |
| */ |
| if (rel->pathlist == NIL) |
| return; |
| inherited_locus = ((Path *) linitial(rel->pathlist))->locus; |
| |
| rows = rel->tuples > 0 ? rel->tuples : 1.0; |
| |
| cp = makeNode(CustomPath); |
| cp->path.pathtype = T_CustomScan; |
| cp->path.parent = rel; |
| cp->path.pathtarget = rel->reltarget; |
| cp->path.param_info = NULL; |
| cp->path.parallel_aware = false; |
| cp->path.parallel_safe = true; |
| cp->path.parallel_workers = 0; |
| cp->path.pathkeys = NIL; |
| cp->path.rows = rows; |
| cp->path.startup_cost = 0; |
| cp->path.total_cost = rows * 0.001; /* cheaper than seqscan to win */ |
| cp->path.locus = inherited_locus; |
| cp->flags = 0; |
| /* |
| * A freshly built SeqScan path is the wrapper's child. It must be a new |
| * node (not one already in rel->pathlist): add_path() below may pfree a |
| * dominated seqscan path, which would leave a dangling child pointer. |
| */ |
| cp->custom_paths = list_make1(create_seqscan_path(root, rel, NULL, 0)); |
| cp->custom_private = NIL; |
| cp->methods = &pcs_path_methods; |
| add_path(rel, &cp->path, root); |
| |
| if (rel->consider_parallel) |
| { |
| CdbPathLocus parallel_locus = cdbpathlocus_from_baserel(root, rel, 2); |
| |
| pp = makeNode(CustomPath); |
| pp->path.pathtype = T_CustomScan; |
| pp->path.parent = rel; |
| pp->path.pathtarget = rel->reltarget; |
| pp->path.param_info = NULL; |
| pp->path.parallel_aware = true; |
| pp->path.parallel_safe = true; |
| pp->path.parallel_workers = parallel_locus.parallel_workers; |
| pp->path.pathkeys = NIL; |
| pp->path.rows = rows / Max(2, parallel_locus.parallel_workers); |
| pp->path.startup_cost = 0; |
| pp->path.total_cost = pp->path.rows * 0.0001; /* much cheaper */ |
| pp->path.locus = parallel_locus; |
| pp->flags = 0; |
| /* Parallel-aware child so it splits blocks across workers. */ |
| pp->custom_paths = |
| list_make1(create_seqscan_path(root, rel, NULL, |
| parallel_locus.parallel_workers)); |
| pp->custom_private = NIL; |
| pp->methods = &pcs_path_methods; |
| add_partial_path(rel, &pp->path); |
| } |
| } |
| |
| Datum |
| pcs_get_hook_calls(PG_FUNCTION_ARGS) |
| { |
| TupleDesc tupdesc; |
| HeapTuple tuple; |
| Datum values[5]; |
| bool nulls[5] = {false, false, false, false, false}; |
| |
| if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) |
| ereport(ERROR, |
| (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), |
| errmsg("function returning record called in context that cannot accept type record"))); |
| |
| tupdesc = BlessTupleDesc(tupdesc); |
| |
| values[0] = Int64GetDatum(pcs_n_estimate); |
| values[1] = Int64GetDatum(pcs_n_init_dsm); |
| values[2] = Int64GetDatum(pcs_n_reinit_dsm); |
| values[3] = Int64GetDatum(pcs_n_init_worker); |
| values[4] = Int64GetDatum(pcs_n_shutdown); |
| |
| tuple = heap_form_tuple(tupdesc, values, nulls); |
| PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); |
| } |
| |
| void |
| _PG_init(void) |
| { |
| DefineCustomBoolVariable("parallel_customscan.enabled", |
| "Replace seqscan paths with TestParallelCustomScan.", |
| NULL, |
| &pcs_enabled, |
| false, |
| PGC_USERSET, |
| 0, |
| NULL, NULL, NULL); |
| |
| RegisterCustomScanMethods(&pcs_scan_methods); |
| |
| prev_pathlist_hook = set_rel_pathlist_hook; |
| set_rel_pathlist_hook = pcs_set_rel_pathlist; |
| } |
| |
| void |
| _PG_fini(void) |
| { |
| set_rel_pathlist_hook = prev_pathlist_hook; |
| } |