Skip to content

DM-55571: Czar-level EXPLAIN query support - #1086

Open
malensek wants to merge 6 commits into
mainfrom
tickets/DM-55571
Open

DM-55571: Czar-level EXPLAIN query support#1086
malensek wants to merge 6 commits into
mainfrom
tickets/DM-55571

Conversation

@malensek

@malensek malensek commented Aug 25, 2026

Copy link
Copy Markdown
Member

This adds support for EXPLAIN <query> that runs at the czar to analyze queries before they execute. This could be particularly useful in situations where a user wants to understand how their query is being scheduled, how complex it is, or how much time it may take to execute.

Currently, the following information is reported:

  • Parsed SQL Representation
  • Qserv IR
  • Booleans (Y/N): needs_merge, chunked, scan_interactive
  • Chunks matched (0 if pruned + isDummy)
  • Restrictors applied
  • Merge SQL (if any)
  • Scan rating
  • Scan table info
  • Worker SQL

JSON support is also available via EXPLAIN FORMAT=JSON <query>

Known limitations:

  • Ideally, it should be possible to issue EXPLAIN queries to workers and collect all of the results into a single comprehensive report. If this incurs too much additional latency, we could support both "shallow" and "deep" variants, or something like EXPLAIN FULL <query>. There is no standardized spec for EXPLAIN behavior so we have some flexibility here.
  • The current implementation is not aware of queries that can execute entirely on the czar, such as SELECT COUNT(*). One possible workaround is directly executing SELECT queries to their fullest extent and capturing the information there instead of registering a new query type.
  • This PR will currently not merge cleanly on the xrd branch, undergoing investigation... . Partially resolved by adding a new query-only ScanTableInfo to separate concerns between the query/frontend and backend layers. The rest of the minor tweaks went to a separate PR.

Testing:

EXPLAIN FORMAT=JSON
  SELECT filterId,
         COUNT(*) AS detections,
         AVG(psfFlux) AS meanPsfFlux
  FROM Source
  GROUP BY filterId
  ORDER BY detections DESC
  LIMIT 6 \G

Gives us:

*************************** 1. row ***************************
EXPLAIN: {
  "area_restrictors": "none",
  "chunks_matched": 14,
  "is_chunked": true,
  "merge_sql": "SELECT `filterId` AS `filterId`,SUM(`QS1_COUNT`) AS `detections`,(SUM(`QS3_SUM`)/SUM(`QS2_COUNT`)) AS `meanPsfFlux` FROM `qcase01`.`Source` AS `qcase01.Source` GROUP BY `filterId` ORDER BY `detections` DESC LIMIT 6",
  "needs_merge": true,
  "parsed_ir": "SelectStmt(SelectList(ValueExpr(\"filterId\", FactorOp(ValueFactor(ColumnRef(\"TableRef(\"qcase01\", \"Source\", \"qcase01.Source\")\", \"filterId\")), query::ValueExpr::NONE)), ValueExpr(\"detections\", FactorOp(ValueFactor(query::ValueFactor::AGGFUNC, FuncExpr(\"COUNT\", ValueExpr(\"\", FactorOp(ValueFactor(STAR), query::ValueExpr::NONE)))), query::ValueExpr::NONE)), ValueExpr(\"meanPsfFlux\", FactorOp(ValueFactor(query::ValueFactor::AGGFUNC, FuncExpr(\"AVG\", ValueExpr(\"\", FactorOp(ValueFactor(ColumnRef(\"TableRef(\"qcase01\", \"Source\", \"qcase01.Source\")\", \"psfFlux\")), query::ValueExpr::NONE)))), query::ValueExpr::NONE))), FromList(TableRef(\"qcase01\", \"Source\", \"qcase01.Source\")), nullptr, OrderByClause(OrderByTerm(ValueExpr(\"detections\", FactorOp(ValueFactor(query::ValueFactor::AGGFUNC, FuncExpr(\"COUNT\", ValueExpr(\"\", FactorOp(ValueFactor(STAR), query::ValueExpr::NONE)))), query::ValueExpr::NONE)), query::OrderByTerm::DESC, \"\")), GroupByClause(GroupByTerm(ValueExpr(\"filterId\", FactorOp(ValueFactor(ColumnRef(\"TableRef(\"qcase01\", \"Source\", \"qcase01.Source\")\", \"filterId\")), query::ValueExpr::NONE)), \"\")), nullptr, 0, 6)",
  "parsed_sql": "SELECT `qcase01.Source`.`filterId` AS `filterId`,COUNT(*) AS `detections`,AVG(`qcase01.Source`.`psfFlux`) AS `meanPsfFlux` FROM `qcase01`.`Source` AS `qcase01.Source` GROUP BY `filterId` ORDER BY `detections` DESC LIMIT 6",
  "scan_interactive": false,
  "scan_rating": 1,
  "scan_tables": [
    {
      "db": "qcase01",
      "lock_in_memory": true,
      "rating": 1,
      "table": "Source"
    }
  ],
  "secidx_restrictors": "none",
  "worker_sql": "SELECT `qcase01.Source`.`filterId` AS `filterId`,COUNT(*) AS `QS1_COUNT`,COUNT(`qcase01.Source`.`psfFlux`) AS `QS2_COUNT`,SUM(`qcase01.Source`.`psfFlux`) AS `QS3_SUM` FROM `qcase01`.`Source_%C\u0007C%` AS `qcase01.Source` GROUP BY `filterId` ORDER BY COUNT(*) DESC"
}
1 row in set (0.02 sec)

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds czar-side EXPLAIN support without dispatching worker jobs.

Changes:

  • Parses traditional and JSON EXPLAIN syntax.
  • Computes query plans, restrictors, chunk coverage, and scan metadata.
  • Returns analysis through temporary result tables.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
src/qproc/QuerySession.h Exposes chunk setup.
src/qproc/QuerySession.cc Centralizes chunk computation.
src/proxy/test.sh Adds EXPLAIN smoke tests.
src/proxy/mysqlProxy.lua Allows EXPLAIN routing.
src/ccontrol/UserQueryType.h Declares EXPLAIN detection.
src/ccontrol/UserQueryType.cc Parses EXPLAIN syntax.
src/ccontrol/UserQuerySelect.cc Uses shared chunk setup.
src/ccontrol/UserQueryFactory.cc Creates EXPLAIN queries.
src/ccontrol/UserQueryExplain.h Defines the EXPLAIN query type.
src/ccontrol/UserQueryExplain.cc Produces tabular or JSON reports.
src/ccontrol/testUserQueryType.cc Tests EXPLAIN recognition.
src/ccontrol/CMakeLists.txt Builds new implementation.

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

Comment on lines +231 to +233
try {
// Compute chunk coverage
qs->setupChunking(sharedResources->secondaryIndex);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nice catch, Copilot! That will call applyFinal on all the plugins... and it's a no-op on all but one, ScanTablePlugin! 👍

@malensek
malensek force-pushed the tickets/DM-55571 branch 2 times, most recently from a1783cb to b2be30c Compare August 25, 2026 04:48
Each of these isX() helper functions are called by UserQueryFactory
(sometimes multiple calls) and carried a TRACE-level log that resulted
in the function name and query string being printed repeatedly. Since
the information is already printed at the DEBUG level (both the query
string and how it ends up being handled), these can be safely removed.
ScanTableInfo was previously used on both sides of the aisle between the
front-end query analysis layer and the back-end worker dispatch. This
separates them into two distinct versions to avoid dependencies reaching
across the aisle.
This chunk coverage computation already relied on several parts of
QuerySession, and relocating it allows us to use it both during
construction of SELECT queries as well as EXPLAIN queries.
This includes a new query type for czar-level EXPLAIN support and the
required plumbing to execute it.

Since this version of EXPLAIN only runs on the czar, it calculates a
variety of query information accessible there, such as the scan rating
and number of chunks that will be involved in the query being analyzed.

Note that this does *NOT* dispatch EXPLAIN queries to the workers for
analysis downstream from the czar.
These tests exercise the syntax surrounding EXPLAIN queries, which is
handled in UserQueryFactory and detected via regex.
@malensek
malensek marked this pull request as ready for review August 28, 2026 22:38

@fritzm fritzm left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Looks good to me -- thanks!

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