Summary
POST _plugins/_ppl with {"profile": true} returns rich per-stage diagnostics for SHARD_FRAGMENT and COORDINATOR_REDUCE stages — a physical_plan string and a ~60-field data_node_metrics block per task. The LATE_MATERIALIZATION stage returns neither. When that stage dominates wall clock, the profile reports where time went only at whole-stage granularity, and root-causing requires reading engine source.
Evidence
Same query, four stages. Stage 2 is 98.5% of total time and the only stage with no
diagnostics at all:
{
"stage_id": 2,
"execution_type": "LATE_MATERIALIZATION",
"elapsed_ms": 5037,
"rows_processed": 0,
"tasks": [
{
"node": "2.0",
"state": "FINISHED",
"elapsed_ms": 5037
}
]
}
Contrast a SHARD_FRAGMENT task in the same response, which carries both
(data_node_metrics abridged — the real block has ~60 fields):
{
"node": "<node-id>/shard[0]",
"state": "FINISHED",
"elapsed_ms": 78,
"physical_plan": "SortPreservingMergeExec: [...] fetch=50\n SortExec: TopK(fetch=50) ...\n QueryShardExec: partitions=2, segments=14, ordering=unsorted\n",
"data_node_metrics": {
"parquet_read_time": 810312,
"bytes_scanned": 30746442,
"rows_pruned_by_page_index": 9669488,
"row_groups_skipped": 12,
"pages_pruned": 847,
"pages_total": 871,
"elapsed_compute": 45083437
}
}
Secondary problem: reported elapsed does not reconcile with timestamps
Where data_node_metrics is present, its start_timestamp / end_timestamp (ns) span far less than the task's elapsed_ms:
| Stage |
Timestamp span |
Reported elapsed_ms |
Unexplained |
| 1 (COORDINATOR_REDUCE) |
33.4 ms |
79 |
45.6 ms |
| 3 (COORDINATOR_REDUCE, wraps stage 2) |
117.5 ms |
5038 |
4920.5 ms |
Stage 3 wraps stage 2, so ~4.9 s is time the profile attributes to a stage while its own instrumentation accounts for 117 ms of it. Whether the remainder is queueing, blocking on a child stage, or genuinely uninstrumented work is not determinable from the output.
Also missing: which rows/bytes the LM stage actually touched
rows_processed: 0 on a stage that materialized a row is at best confusing. There is no counter for the bytes or column chunks the fetch path read — precisely the number needed to detect the full-column-chunk problem in the companion issue.
Impact
Diagnosing a 5-second single-row query required checking out OpenSearch, arrow-rs, and datafusion and tracing four call sites by hand. The profile correctly said which stage was slow and gave no information about why. For anyone without local source checkouts — including customers and most on-call paths — this is a dead end.
Requested
- Emit
physical_plan for LATE_MATERIALIZATION tasks, as other stage types do.
- Emit
data_node_metrics for the fetch path. Minimum useful set, mirroring
existing scan-path metric names:
bytes_scanned, parquet_read_time, files_opened, row_groups_processed
- a count of column chunks fetched and whether the offset index was
available (directly distinguishes page-level from whole-chunk fetch)
metadata_load_time, elapsed_compute
- Populate
rows_processed for the stage, or drop the field where not meaningful.
- Reconcile
elapsed_ms with instrumented time, or split it into explicit
queue_time / wait_time / compute_time so the gap is legible rather than
silent.
Notes
"profile": true on _plugins/_ppl appears to be undocumented — it is not in the PPL docs and was found by trial. It returns summary.total_time_ms, per-phase timings (analyze/optimize/execute/format), plan.full_plan, plan.query_id, and the per-stage tree described above. Worth documenting independently of this issue; it is the fastest plan-capture path on a managed domain, where tailing node logs for RustLoggerBridge DEBUG output is not available.
The query_id returned in plan does allow correlating a run against node logs where log access exists — but the Calcite-level full_plan does not include the DataFusion ordering=sorted=[...] / ordering=unsorted line, so sort-propagation questions still require the log route.
Related
- Companion issue: late-materialization fetch path reads full column chunks because the offset index is never loaded — found by source reading, not profiling, because of this gap
Summary
POST _plugins/_pplwith{"profile": true}returns rich per-stage diagnostics forSHARD_FRAGMENTandCOORDINATOR_REDUCEstages — aphysical_planstring and a ~60-fielddata_node_metricsblock per task. TheLATE_MATERIALIZATIONstage returns neither. When that stage dominates wall clock, the profile reports where time went only at whole-stage granularity, and root-causing requires reading engine source.Evidence
Same query, four stages. Stage 2 is 98.5% of total time and the only stage with no
diagnostics at all:
{ "stage_id": 2, "execution_type": "LATE_MATERIALIZATION", "elapsed_ms": 5037, "rows_processed": 0, "tasks": [ { "node": "2.0", "state": "FINISHED", "elapsed_ms": 5037 } ] }Contrast a
SHARD_FRAGMENTtask in the same response, which carries both(
data_node_metricsabridged — the real block has ~60 fields):{ "node": "<node-id>/shard[0]", "state": "FINISHED", "elapsed_ms": 78, "physical_plan": "SortPreservingMergeExec: [...] fetch=50\n SortExec: TopK(fetch=50) ...\n QueryShardExec: partitions=2, segments=14, ordering=unsorted\n", "data_node_metrics": { "parquet_read_time": 810312, "bytes_scanned": 30746442, "rows_pruned_by_page_index": 9669488, "row_groups_skipped": 12, "pages_pruned": 847, "pages_total": 871, "elapsed_compute": 45083437 } }Secondary problem: reported elapsed does not reconcile with timestamps
Where
data_node_metricsis present, itsstart_timestamp/end_timestamp(ns) span far less than the task'selapsed_ms:elapsed_msStage 3 wraps stage 2, so ~4.9 s is time the profile attributes to a stage while its own instrumentation accounts for 117 ms of it. Whether the remainder is queueing, blocking on a child stage, or genuinely uninstrumented work is not determinable from the output.
Also missing: which rows/bytes the LM stage actually touched
rows_processed: 0on a stage that materialized a row is at best confusing. There is no counter for the bytes or column chunks the fetch path read — precisely the number needed to detect the full-column-chunk problem in the companion issue.Impact
Diagnosing a 5-second single-row query required checking out
OpenSearch,arrow-rs, anddatafusionand tracing four call sites by hand. The profile correctly said which stage was slow and gave no information about why. For anyone without local source checkouts — including customers and most on-call paths — this is a dead end.Requested
physical_planforLATE_MATERIALIZATIONtasks, as other stage types do.data_node_metricsfor the fetch path. Minimum useful set, mirroringexisting scan-path metric names:
bytes_scanned,parquet_read_time,files_opened,row_groups_processedavailable (directly distinguishes page-level from whole-chunk fetch)
metadata_load_time,elapsed_computerows_processedfor the stage, or drop the field where not meaningful.elapsed_mswith instrumented time, or split it into explicitqueue_time/wait_time/compute_timeso the gap is legible rather thansilent.
Notes
"profile": trueon_plugins/_pplappears to be undocumented — it is not in the PPL docs and was found by trial. It returnssummary.total_time_ms, per-phase timings (analyze/optimize/execute/format),plan.full_plan,plan.query_id, and the per-stage tree described above. Worth documenting independently of this issue; it is the fastest plan-capture path on a managed domain, where tailing node logs forRustLoggerBridgeDEBUG output is not available.The
query_idreturned inplandoes allow correlating a run against node logs where log access exists — but the Calcite-levelfull_plandoes not include the DataFusionordering=sorted=[...]/ordering=unsortedline, so sort-propagation questions still require the log route.Related