Skip to content

Feature: add pg_query_state runtime API to gp_stats_collector - #52

Open
roaldm153 wants to merge 17 commits into
open-gpdb:REL_2_STABLEfrom
roaldm153:pgqs-signal-only
Open

Feature: add pg_query_state runtime API to gp_stats_collector#52
roaldm153 wants to merge 17 commits into
open-gpdb:REL_2_STABLEfrom
roaldm153:pgqs-signal-only

Conversation

@roaldm153

Copy link
Copy Markdown

What does this PR do?

Adds a signal-based runtime query-state facility to the gp_stats_collector extension. It lets a session inspect the live execution state of another running backend on demand - walking its active plan tree across the QD and all QEs - without waiting for the query to finish.

New SQL API (extension v1.2, schema gpsc):

  • pg_query_state(pid) - fan out a poll to the query running on pid; each participating backend walks its plan tree and logs a per-node snapshot.
  • pg_query_state_backends(pid) - list the (segid, pid) QE backends taking part in that query.
  • cbdb_mpp_query_state(gp_segment_pid[]) - QE-side dispatch target.

The extension embeds the pg_query_state signal layer, which depends on three PostgreSQL core changes folded directly into the tree (configure enables the extension by default, so the tree must build without a manual patch step):

  • custom ProcSignal handlers (procsignal.c/.h, postgres.c);
  • end-of-node instrumentation flag readable mid-run (instrument.c/.h);
  • runtime EXPLAIN entry points (explain.c/.h).

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Breaking Changes

None. Core changes are additive (new signal reasons, a new Instrumentation field, new runtime-EXPLAIN paths); existing behavior is unchanged.

Test Plan

  • Unit tests added/updated

  • Integration tests added/updated

  • Passed make installcheck

  • pg_regress (gpcontrib/gp_stats_collector/test): catalog contract (function/type registration, exec location) and input-validation errors.

  • isolation2 (gpcontrib/gp_stats_collector/test/isolation2), multi-session:

    • gpsc_pqs_backends - idle backend yields an empty list;
    • gpsc_pqs_running - happy path: poll a query suspended on the QEs via a fault injector;
    • gpsc_pqs_perms - permission gate (non-owner non-superuser denied);
    • gpsc_pqs_disabled - STAT_DISABLED reports no backends;
    • gpsc_pqs_seg_count - one backend per primary segment.
  • A dedicated CI workflow builds Cloudberry with the extension across ubuntu22.04/rocky8/rocky9 and runs the pg_regress, isolation2, and core regression suites.

Impact

Performance:
No steady-state cost. Work happens only when a poll is issued: one signal fan-out plus a bounded shared-memory round-trip; the shmem lock is held only around the request, not during dispatch.

User-facing changes:
New gpsc.pg_query_state* functions and the pg_query_state.enable GUC. Functions are granted to PUBLIC; access is gated in C so a caller may poll a backend only if it is a superuser or owns the target query.

Dependencies:
None.

Checklist

  • Followed contribution guide
  • Added/updated documentation
  • Reviewed code for security implications
  • Requested review from cloudberry committers

Additional Context

The signal layer under src/pg_query_state/ is derived from pg_query_state (PostgreSQL License). Derived files carry dual license headers (ASF + Portions Copyright Postgres Professional) and the root LICENSE records a PostgreSQL-License notice for it. Happy to provide provenance details for IP clearance.

Fold three PostgreSQL core changes the gp_stats_collector extension depends
on directly into the sources: custom ProcSignal handlers (procsignal.c/.h,
postgres.c), end-of-node instrumentation (instrument.c/.h), and runtime
EXPLAIN entry points (explain.c/.h). configure enables the extension by
default, so the tree must build without a manual patch step.
Signal-dispatch module and executor-lifecycle hooks that walk the live plan
tree of a running backend on demand, plus the SQL API (extension v1.2):
pg_query_state(pid), pg_query_state_backends(pid), cbdb_mpp_query_state().

Functions are granted to PUBLIC and guarded in C: a caller may poll a
backend only if it is a superuser or owns the target query (GetUserId() ==
proc->roleId), so monitoring agents can run as a non-superuser while access
stays restricted.
Builds Cloudberry with the extension across ubuntu22.04/rocky8/rocky9,
stands up a demo cluster with gp_stats_collector preloaded, and runs the
pg_regress and multi-session isolation2 suites.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, @roaldm153 welcome!🎊 Thanks for taking the effort to make our project better! 🙌 Keep making such awesome contributions!

pg_regress contract: catalog registration and input-validation errors.
isolation2 (pg_query_state multi-session): idle backend, happy-path poll of
a suspended query, the permission gate, STAT_DISABLED, and a strict
one-backend-per-primary-segment count.
The signal-dispatch layer under src/pg_query_state/ derives from
pg_query_state (https://github.com/postgrespro/pg_query_state, PostgreSQL
License). Add dual license headers (ASF + Portions Copyright Postgres
Professional) to the derived files (pg_query_state.c/.h, signal_handler.c),
an ASF header to qs_types.h (original Cloudberry code, the stray Postgres
Professional line removed), and a PostgreSQL-License notice for
pg_query_state in the root LICENSE.
pg_qs_executor_start() enabled instrumentation and called
cdbexplain_showExecStatsBegin() on both QD and QE backends.  That
function aggregates per-QE statistics on the dispatcher and opens with
Assert(Gp_role != GP_ROLE_EXECUTE), so on a QE it trips the assertion in
a cassert build (a harmless no-op in a release build, which is why it
went unnoticed).  The DTX recovery worker runs the executor on segments
with Gp_role == GP_ROLE_EXECUTE, so distributed-transaction recovery
crashed the segment and left the cluster looping in reset/recovery mode.

Guard the call with GP_ROLE_DISPATCH.  QE backends still receive
instrument_options, which is all the per-node plan-tree walker reads;
the showstatctx aggregation is a coordinator-only concern.
Threads a 16-byte trace_id through the collection so it matches the catalog
contract of the UDS per-node branch while this variant keeps writing per-node
snapshots to the server log:

- pg_query_state(pid, trace_id bytea) and cbdb_mpp_query_state(gp_segment_pid[],
  trace_id bytea); trace_id is validated to 16 bytes and stamped into a new
  per-backend shmem slot (toc key 3) before each backend is signalled, so the
  handler knows which collection its snapshot belongs to.
- SendCdbComponents sizes the backend-info reply by the number of descriptors it
  actually emits (sum of each segment's activelist) rather than numActiveQEs;
  the two diverge for coordinator-heavy plans (INSERT ... SELECT) and overran
  the allocation, which the receiver rejected as "unexpected message length".
- SQL migrations, catalog-contract and isolation2 specs updated for the new
  signatures.
Remove one-line comments that paraphrase the declaration or the next statement
(the saved-hook/module-initialized globals, the outer/inner walker recursion).
Keep the ones that carry non-obvious rationale.
pg_qs_executor_end walked the whole plan tree and logged per-node stats
for every query on every backend, even when pg_query_state was disabled
or the query was never instrumented.  Gate the walk on the instrumented
marker (queryDesc->totaltime, set in pg_qs_executor_start under the same
predicate) so uninstrumented queries pay nothing.

cbdb_mpp_query_state only checked for a NULL proc before indexing
qs_trace_slots[proc->backendId] and signalling; an InvalidBackendId (-1)
would underflow the slot array and misroute the signal.  Skip it, matching
the QD entry point.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a signal-driven “runtime query state” facility to Cloudberry’s gp_stats_collector extension by embedding a pg_query_state-derived signal layer and introducing small additive hooks in core PostgreSQL/GPDB areas (ProcSignal, instrumentation, and EXPLAIN) to allow mid-execution sampling across QD/QEs.

Changes:

  • Introduces custom ProcSignal reasons/handlers and plumbing to register/dispatch extension-defined signals.
  • Extends executor instrumentation and EXPLAIN to support mid-run (“runtime”) observation without finalizing node stats.
  • Adds gp_stats_collector extension v1.2 SQL API plus pg_regress + isolation2 test suites and a dedicated CI workflow.

Reviewed changes

Copilot reviewed 40 out of 40 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/include/storage/procsignal.h Adds custom ProcSignal slots/reasons and APIs for registering/handling custom signals.
src/include/executor/instrument.h Adds Instrumentation.eof to support safe mid-run “node exhausted” detection.
src/include/commands/explain.h Adds ExplainState.runtime flag for runtime explain paths.
src/backend/tcop/postgres.c Wires custom signal handling into interrupt processing; adds -Z flag passthrough.
src/backend/storage/ipc/procsignal.c Implements custom ProcSignal registration and custom-signal handling loop.
src/backend/executor/instrument.c Tracks and resets new Instrumentation.eof field.
src/backend/commands/explain.c Adjusts EXPLAIN/ANALYZE reporting for runtime mode and mid-run snapshots.
pom.xml Updates licensing-scan exclusions for new test harness files.
LICENSE Adds PostgreSQL-License notice for pg_query_state-derived code.
gpcontrib/gp_stats_collector/test/sql/gpsc_pg_query_state.sql Adds pg_regress coverage for catalog contract and negative paths.
gpcontrib/gp_stats_collector/test/Makefile Adds installcheck harness for the new pg_regress suite.
gpcontrib/gp_stats_collector/test/isolation2/sql/setup.sql Adds shared isolation2 setup (extensions).
gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_seg_count.sql Adds isolation2 test for “one QE backend per primary segment”.
gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_running.sql Adds isolation2 happy-path test for polling a suspended running query.
gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_perms.sql Adds isolation2 permissions gate coverage.
gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_disabled.sql Adds isolation2 coverage for per-session module disable behavior.
gpcontrib/gp_stats_collector/test/isolation2/sql/gpsc_pqs_backends.sql Adds isolation2 coverage for polling an idle backend.
gpcontrib/gp_stats_collector/test/isolation2/Makefile Adds installcheck harness for the isolation2 suite.
gpcontrib/gp_stats_collector/test/isolation2/isolation2_schedule Adds isolation2 schedule for the pg_query_state suite.
gpcontrib/gp_stats_collector/test/isolation2/expected/setup.out Expected output for setup.
gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_seg_count.out Expected output for seg-count test.
gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_running.out Expected output for running-query test.
gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_perms.out Expected output for permissions test.
gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_disabled.out Expected output for disabled-mode test.
gpcontrib/gp_stats_collector/test/isolation2/expected/gpsc_pqs_backends.out Expected output for idle-backend test.
gpcontrib/gp_stats_collector/test/isolation2/.gitignore Ignores generated isolation2 artifacts.
gpcontrib/gp_stats_collector/test/expected/gpsc_pg_query_state.out Expected output for pg_regress contract/negative tests.
gpcontrib/gp_stats_collector/src/pg_query_state/signal_handler.c Implements signal handlers and plan-tree walking/logging for snapshots.
gpcontrib/gp_stats_collector/src/pg_query_state/qs_types.h Defines per-node sample struct and enums for runtime snapshots.
gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.h Defines shared-memory + protocol structs and public APIs for pg_query_state layer.
gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c Implements shmem setup, GUCs, executor hooks, and SQL entry points.
gpcontrib/gp_stats_collector/src/hook_wrappers.cpp Integrates pg_query_state executor lifecycle hooks into existing extension hooks.
gpcontrib/gp_stats_collector/src/GpscStat.cpp Updates shmem hook handling (incl. PG15 shmem_request_hook) and documentation/comments.
gpcontrib/gp_stats_collector/src/gp_stats_collector.c Calls pg_qs_init() from _PG_init() to register shmem/custom signals at startup.
gpcontrib/gp_stats_collector/README.md Documents the new runtime query state API and configuration.
gpcontrib/gp_stats_collector/Makefile Adds include paths needed by new pg_query_state code.
gpcontrib/gp_stats_collector/gp_stats_collector.control Bumps extension default version to 1.2.
gpcontrib/gp_stats_collector/gp_stats_collector--1.2.sql Adds v1.2 objects: types, functions, grants for pg_query_state API.
gpcontrib/gp_stats_collector/gp_stats_collector--1.1--1.2.sql Adds upgrade script introducing pg_query_state API.
.github/workflows/gpsc-ci.yaml Adds dedicated CI workflow covering build + regress + isolation2 for the extension.
Suppressed comments (1)

gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.h:111

  • The comment references TIMINIG_OFF_WARNING (misspelled). If an alias is kept for compatibility, the primary documented name should be TIMING_OFF_WARNING.
	int warnings;         /* bitmask of TIMINIG_OFF_WARNING / BUFFERS_OFF_WARNING */

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c Outdated
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c Outdated
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.h
Comment thread gpcontrib/gp_stats_collector/README.md Outdated
Comment thread src/backend/storage/ipc/procsignal.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c Outdated
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c Outdated
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c
Comment thread gpcontrib/gp_stats_collector/src/pg_query_state/pg_query_state.c Outdated
Comment thread .github/workflows/gpsc-ci.yaml
Bring the signal-only base up to the full trace_id/UDS pipeline:

- Key every collection by a 16-byte trace_id minted per pg_query_state()
  invocation; the SQL API gains a bytea trace_id argument and the
  dispatcher stamps each target's per-backend trace slot before signalling.
- Push per-node batches and the coordinator plan-doc to the local yagpcc
  over UDS as protobuf (SetPerNodeBatchReq / SetQueryPlanReq) instead of
  the server log; add the C++ PlanNodeEmitter bridge and the yagpcc protos.
- Emit derived per-node rate fields (ntuples_delta, tuples_per_sec,
  time_since_init_sec, stalled) computed on the C side from a per-node
  rolling state, reset on executor start/end.
- Harden the UDS connect/send path against backpressure (bounded poll),
  report the coordinator itself for QD-only queries, and apply the
  review-driven lock/leak/NULL-safety fixes to the entry points.
run.sh -c local ships the whole repo as the Docker build context. Without
this, object files, shared libs and generated protobuf sources from a host
or devcontainer build get copied into the image; the in-image make reuses
those stale .o (compiled against the dev environment's Xerces/GSS headers)
and the link fails with undefined xercesc_3_2::* / pqsecure_open_gss.
Excluding them forces a clean in-image build.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants