Skip to content

Shared scans as candidates at ingestion and query time; charge each scan the plan runs #1179

Open

Description

@zzylol

Problem, by example

Ingestion time. Two summaries are maintained at ingestion time over one source m, with 6 series and 6 rows/s:

A: SummaryTumblingWindowChain of a KLL, 10000 ms tumbling windows   refresh window (10 s, 0)
B: SummaryTumblingWindowChain of a DDSketch, 60000 ms tumbling windows   refresh window (60 s, 0)
both read: Scan(m), NotMaterialized, IngestionTime

The Scan can run two ways:

  • Separately: one ingestion run per refresh window, and each run scans its own window. m's rows are read twice.
  • Shared: one scan of every arriving row, at the finer cadence, feeding both chains. B's 60 s window is filled across six 10 s runs. Rows are read once.

Today this choice is not a candidate. #1151 (#1152–#1154) hard-codes it in the cost function: asap_cost::execution_demand::computations charges a NotMaterialized ingestion-time node once per distinct refresh window of its MatAtIngestion consumers. The design doc says so (docs/design_docs/physical-planning-and-deployment.md, "Refresh groups"): "a stateless ingestion-time node is charged once per refresh window of the views above it". The #1095 group MILP (#1144, #1157) mirrors that rule with one ingestion context I_r per refresh window.

So:

  • A and B always pay for two scans, and the plan cannot choose the shared scan.
  • A runtime that refreshes per refresh window pays what was priced. A runtime that "updates states sample by sample passes each sample once whatever the windows, so it runs cheaper than priced" (same doc). Its real cost and the priced cost differ, and nothing in the plan says which runtime behaviour was assumed.
  • The grouping rule lives in asap-cost. There is no node in the plan for it, so the number of scans the plan pays for is not visible in the selected DAG.

Query time. rate(m[1m]) and rate(m[5m]), both every 10 s, computed at query time over raw data:

  • Separately: one scan of the last 1 min and one of the last 5 min, i.e. 6 + 30 = 36 s-equivalents of rows per 10 s.
  • Shared: one scan of the last 5 min, the largest scope. The 1 min reader keeps its last minute with a TimeRange. That is 30 s-equivalents.

(Both figures are hand-counted for illustration, not measured.)

#951 proposes this as a Pass 2 rule for different predicates, labels and time ranges, at query time. It does not cover ingestion time, and it does not settle how a shared scan is charged.

What should happen instead:

Why it happens

Proposal

1. A shared-scan rule generates candidates at both timings.

2. Ingestion time needs a capability and a runtime contract. A shared ingestion scan at a 10 s cadence feeding a 60 s chain needs the runtime to keep the 60 s chain's newest tumbling window open across six runs, and to add each run's rows to it. That is incremental update, not one refresh per window.

  • The deployment declares whether it supports this, next to raw_data_retained and query_time_results_retained.
  • Without that capability, only the separate (per refresh window) candidates exist, which is today's behaviour.

3. Cost: charge what runs.

4. The plan shows the scans it pays for. The selected DAG has one Scan node per scan execution kind. The runtime follows the plan: one shared scan, or one scan per refresh window.

Scope and acceptance

In scope:

Out of scope:

Acceptance:

  • Ingestion, with the capability. For A and B above:
    • the memo has both a shared-scan candidate and per-window scan candidates;
    • the selected plan uses the shared scan when it is cheaper;
    • plan_cost charges one scan of m's rows.
  • Ingestion, without the capability. The plan and its cost are as today: one scan per refresh window.
  • Query time. For rate(m[1m]) and rate(m[5m]) every 10 s, the memo has a shared 5 min scan read by the 1 min reader through TimeRange(1m). plan_cost charges the scans the selected plan runs.
  • Correctness. runtime_install.rs runs both plans with IngestionBuilds::Tumbling (per window) and a new incremental mode. Every answer equals the query-time reference.
  • Objective. In the [Feature] Stage 3: no execution-scope copies, exact candidate-level MILP, one cost formula #1095 MILP, objective == plan_cost on these workloads and the memo examples.
  • No regressions. corpus_snapshots and sketch_plan_snapshots keep every selected plan's cost or lower it; the PR lists each changed plan.

Status and related work

Activity

  1. added
    enhancementNew feature or request
    cost-modelCost formulas, statistics, ranking, and selection
    optimizerWorkload optimization and plan selection
    on Nov 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    CSEcost-modelCost formulas, statistics, ranking, and selectionenhancementNew feature or requestoptimizerWorkload optimization and plan selection

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions