From 1cb0c2cc2b816aa8e80927ca729d59d278e5cc15 Mon Sep 17 00:00:00 2001 From: zz_y Date: Fri, 28 Aug 2026 14:51:57 -0600 Subject: [PATCH 01/11] docs(design): model workload demand and summary lifecycle --- docs/design_docs/asap-aware-mapping/README.md | 3 + .../workload-demand-and-summary-lifecycle.md | 586 ++++++++++++++++++ 2 files changed, 589 insertions(+) create mode 100644 docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md diff --git a/docs/design_docs/asap-aware-mapping/README.md b/docs/design_docs/asap-aware-mapping/README.md index 1d32afe..9e7ddaf 100644 --- a/docs/design_docs/asap-aware-mapping/README.md +++ b/docs/design_docs/asap-aware-mapping/README.md @@ -90,6 +90,9 @@ The design is split into focused documents: summaries and optimizations can be composed safely. - [End-to-end accuracy guarantees](end-to-end-accuracy-guarantees.md) specifies the typed guarantee IR, sketch contracts, composition rules, target checking, and fail-closed boundaries. +- [Workload demand and summary lifecycle](workload-demand-and-summary-lifecycle.md) separates + query demand from data workload and defines ephemeral, prepared, shared, and continuously + maintained summary-state alternatives. - [Explainability](explainability.md) describes how the planner reports available replacements using the same candidate space it optimizes. diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md new file mode 100644 index 0000000..0c653a1 --- /dev/null +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -0,0 +1,586 @@ +# Design: Workload Demand and Summary Lifecycle + +## Audience and context + +This document is for ASAPPlanner designers, architects, researchers, and +developers working on workload-aware plan selection. It defines how the planner +should describe query demand, data workload, and the lifecycle of summary state. +It is a design contract, not a description of the current public Rust API. + +The terminology follows the ProjectASAP +[glossary](https://github.com/ProjectASAP/internal-docs/blob/03e1c70f5af3ae9221471898541067eee7f86338/glossary.md). +That glossary is authoritative for the meanings of data workload, query +workload, ad-hoc and predictable queries, one-time and repeated queries, +real-time and longitudinal queries, output cardinality, and lookback window. +This document maps those concepts into planner responsibilities and records +where the current model is incomplete. + +This design is orthogonal to +[end-to-end accuracy guarantees](end-to-end-accuracy-guarantees.md). Accuracy +decides whether a candidate is correct enough. Workload demand and state +lifecycle decide whether building, maintaining, sharing, or recomputing that +candidate is worthwhile. Neither decision may override the other. + +## Problem and why now + +A summary operator does not imply one execution lifecycle. The same exact or +approximate summary can be: + +- built once from data at rest and discarded after one query; +- prepared before a known future query and retired afterward; +- shared across a bounded set of requests; or +- maintained incrementally as data continues to arrive. + +Likewise, an exact stateless operator may run once over a batch, once per +update in an incremental pipeline, or once per readout. Operator statefulness, +execution schedule, and output representation are separate properties. + +The query expression alone cannot determine those properties. The same query +may arrive unexpectedly during exploration, run once at a scheduled time, or +repeat every ten seconds on a dashboard. Planning summary state from syntax +alone either misses reuse or invents reuse that the workload does not justify. + +The current normalized workload distinguishes a one-shot `query_batch` from +fixed-interval `repeating_queries`, and the recurrence cost model distinguishes +one-shot consumers from evaluation and update rates. This is a useful base, but +it does not represent predictability, uncertain demand, real-time versus +longitudinal scope, at-rest versus continuously ingesting data, or summary-state +lifecycle. It also risks treating "repeating query" and "streaming data" as the +same fact even though the glossary defines them on different axes. + +## Inputs, outputs, and end-to-end behavior + +The planner receives four logically distinct inputs: + +1. logical queries and their correctness and latency requirements; +2. query-workload demand, including predictability and recurrence; +3. data-workload characteristics, including ingestion and queried time scope; +4. existing summaries and the lifecycle actions available to the deployment. + +The output is a legal physical-plan choice plus explicit state deployments. A +state deployment states whether a summary is ephemeral, prepared, shared for a +bounded period, or continuously maintained. Its cost explanation identifies +the demand and data evidence used in the decision. + +```text +logical queries + requirements + query demand -----+ + data workload ---+--> candidate plans + available summaries ---+ -> semantic and accuracy legality + -> lifecycle alternatives + -> horizon-normalized cost + -> selected plan + deployments +``` + +For an unpredictable one-time query, the planner may read an existing summary, +build an ephemeral summary, or recompute from raw data. It must not assume +future reuse. For a predictable one-time query, it may additionally compare +preparing state in advance with building or recomputing at execution time. For +repeated queries, it may amortize build and maintenance cost across reads over +an explicit horizon. + +## Goals and non-goals + +### Goals + +- Represent glossary-defined query-workload and data-workload concepts without + collapsing independent axes into one enum. +- Separate an operator's statefulness from its execution schedule and the + lifecycle of the state it produces. +- Make unknown demand explicit and fail closed rather than treating it as zero + or infinite reuse. +- Compare one-time and rate-valued costs only through an explicit horizon. +- Explain why a selected plan builds, reuses, maintains, or avoids summary + state. +- Preserve a minimal path from the current batch/repeating workload and + recurrence profile to the proposed model. + +### Non-goals + +- Scheduling jobs, assigning machines, admission control, or executing queries. +- Predicting future query text inside ASAPPlanner. +- Defining a sketch runtime or state-storage protocol. +- Choosing a concrete forecasting algorithm for uncertain demand. +- Changing accuracy targets or guarantee algebra. +- Implementing the proposed public types in this documentation-only PR. + +## Heilmeier questions + +- **What are we trying to do?** Choose whether summary state should be built, + maintained, shared, reused, or avoided for different kinds of query demand + and data workload. +- **How is it done today, and what are the limits?** The planner distinguishes + one-shot counts, fixed repeating intervals, and an ingest-rate proxy. It + cannot distinguish an unexpected exploratory query from a scheduled one-time + report, or data at rest from continuous ingestion as an explicit mode. +- **What is new, and why will it succeed?** Orthogonal workload axes and an + explicit state lifecycle let the existing recurrence formulas compare the + same summary under different deployment choices without changing query + semantics. +- **Who cares?** Users need predictable latency and cost; operators need to + know what state will exist and for how long; planner developers need demand + assumptions to be auditable. +- **What are the risks and costs?** More inputs can make planning harder to + configure, forecasts may be stale, and a large lifecycle search space can + increase planning cost. +- **How long will it take?** The minimum implementation can extend normalized + workload input, lifecycle candidates, and explanations incrementally. Demand + forecasting and runtime state catalogs are later integrations. +- **What are the checks for success?** The acceptance cases below must produce + different lifecycle alternatives and cost terms for identical query syntax + under different workload contracts. + +## Proposed design + +### Authoritative concepts and ownership + +| Concept | Authoritative layer | Reason | +| --- | --- | --- | +| Query meaning | Pre-ASAP query IR | Workload metadata must not change semantics | +| Accuracy and latency requirement | Per-query requirements | Requirements belong to the requested result | +| Query demand | Workload input | Arrival and recurrence are not inferable from syntax | +| Data workload | Workload input | Ingestion and distribution describe the data, not query demand | +| Summary capability | Summary properties | Merge, delete, and update support constrain legal lifecycles | +| State lifecycle | Physical planning decision | Lifecycle is selected, not declared by `SummaryAgg` | +| Cost | Cost model and explanation | Cost consumes all inputs but does not define their meaning | + +### Query workload: three independent axes + +The glossary classifications must be modeled independently. + +#### Predictability + +```rust +enum Predictability { + /// The query shape is not known before arrival. + AdHoc, + /// The query or parameterized template is known before execution. + Predictable { + known_at: Option, + }, + /// The caller supplied no reliable classification. + Unknown, +} +``` + +`AdHoc` does not mean repeated or one-time. It means the query shape was not +known in advance. The glossary currently places exploratory/ad-hoc queries in +the one-time category, so the MVP should accept `AdHoc + OneTime` and reserve +other combinations until a concrete use case establishes their semantics. + +#### Recurrence + +```rust +enum QueryRecurrence { + OneTime { + invocations: u64, + execute_at: Option, + }, + Repeated { + demand: RepeatedDemand, + }, + Unknown, +} + +enum RepeatedDemand { + FixedInterval(Duration), + Scheduled(Vec), + EstimatedRate(DemandEstimate), +} +``` + +One-time means no recurrence is expected for that workload entry. Several +one-time consumers may still share a subplan within a submitted workload. +Repeated means the same query expression over its selected data is evaluated +over time, matching the glossary. Parameterized templates require an explicit +equivalence policy before their executions count as the same query. + +Query-workload volume is more than an average rate. Cost and latency can differ +for the same total request count when requests arrive in bursts or concurrently. +An empirical `DemandEstimate` should therefore be able to carry an observation +window, expected invocation count or rate, peak rate, concurrency, confidence, +and provenance. Fixed intervals and explicit schedules are declarations rather +than estimates and do not need fabricated confidence. The MVP may cost only +invocation count and evaluation rate, but it must preserve unsupported volume +characteristics for explanation rather than silently discarding them. + +#### Queried time scope + +```rust +enum QueryTimeScope { + RealTime, + Longitudinal, + Mixed, + Unknown, +} +``` + +This classification is not derived only from a numeric lookback. A five-minute +lookback over recent data is real-time; the same duration over archived data is +not. Planning input should therefore carry the classification and the concrete +time selection separately: + +```rust +struct TimeSelection { + scope: QueryTimeScope, + lookback: Option, + as_of: Option, +} +``` + +`lookback` is a query property already represented by temporal query nodes in +some frontends. The normalized workload should reference or derive it rather +than introduce a second conflicting value. + +### Data workload is separate from query workload + +```rust +enum DataArrival { + AtRest, + ContinuouslyIngesting, + Mixed, + Unknown, +} + +struct DataWorkload { + arrival: DataArrival, + ingestion_volume: Evidence, + ingestion_rate: Evidence, + input_cardinality: Evidence, + distribution: Evidence, +} +``` + +The current `DataCharacteristics` supplies continuous-ingestion fields such as +series count and samples per second. It should become one source for this model, +not the authoritative definition of all data workloads. Data at rest may have +row count and scan statistics without a nonzero ingestion rate. Unknown arrival +must not be interpreted as continuously ingesting or at rest. + +Every empirical value uses an evidence wrapper conceptually containing: + +```rust +struct Evidence { + value: Option, + source: EvidenceSource, + observed_at: Option, + valid_for: Option, + applicability: Applicability, +} +``` + +This reuses the provenance and freshness principles from empirical summary +parameter configuration. A missing or stale value remains unknown. + +### Output cardinality is a derived or evidenced cost input + +Output cardinality depends on input cardinality and grouping columns. The +planner may derive it analytically, accept a catalog estimate, or leave it +unknown. The source and applicability must be preserved because output +cardinality affects summary size, read cost, post-processing cost, and network +cost. It is not a query correctness requirement. + +### Separate operator state, schedule, and output + +The physical design must not use `SummaryAgg` as shorthand for incremental +maintenance. + +```rust +enum OperatorState { + Stateless, + Stateful { + mergeable: bool, + deletable: bool, + }, +} + +enum EvaluationSchedule { + OneShot, + PerUpdate, + OnRead, +} + +enum OutputRepresentation { + PlainRows, + SummaryState, + FinalizedValue, +} +``` + +A one-shot sketch builder is stateful while it consumes its input, but it does +not imply long-lived incremental maintenance. A stateless transform can run +`PerUpdate` before a downstream maintained summary. These types describe an +execution contract; they do not replace semantic operators in the post-ASAP IR. + +### State lifecycle is a plan alternative + +```rust +enum StateLifecycle { + Ephemeral, + Prepared { + activate_at: Timestamp, + retire_at: Timestamp, + }, + Shared { + retention: Duration, + }, + ContinuouslyMaintained, +} +``` + +- `Ephemeral` builds state for one submitted workload and discards it afterward. +- `Prepared` builds or begins maintaining state before a predictable query and + retires it after the known need ends. +- `Shared` retains state for multiple consumers over a bounded lifetime. +- `ContinuouslyMaintained` applies data updates until an explicit later + deployment decision retires the state. + +The summary family and its properties constrain which lifecycles are legal. +For example, an append-only sketch may support continuous inserts but not a +sliding-window lifecycle requiring deletion. Lifecycle legality is checked +before cost ranking, like accuracy legality. + +### Existing summaries are planning input + +An ad-hoc query cannot justify creating permanent state from unknown future +demand, but it may use compatible state that already exists. The planning +problem therefore needs a state catalog describing identity, parameters, +coverage, freshness, accuracy guarantee, lifecycle, and ownership. Catalog +integration is a separate implementation increment; this design only requires +that "reuse existing" and "create new" remain distinguishable alternatives. + +### Cost over a horizon + +For a stateful incremental alternative over horizon `H`: + +```text +total(H) = build_cost + + H * update_rate * maintenance_cost_per_update + + reads(H) * summary_read_cost + + H * retention_cost_rate + + retirement_cost +``` + +For repeated raw recomputation: + +```text +total(H) = reads(H) * raw_recompute_cost +``` + +For an ephemeral summary: + +```text +total = invocations * (build_cost + summary_read_cost + disposal_cost) +``` + +For prepared state, update and retention terms apply only between activation +and retirement. Existing state does not pay a new build cost, but its catalog +provenance must establish that assumption. + +The existing `Cost`, `CostRate`, `EvaluationRate`, `UpdateRate`, `Horizon`, and +`total_cost` types are the minimum viable foundation. The implementation should +extend their explanations and lifecycle coverage instead of creating a second +recurrence cost system. + +### Unknown and uncertain demand + +Unknown demand is not zero demand and is not evidence of future reuse. The MVP +policy is: + +- do not select newly created long-lived state solely on unknown future reuse; +- allow raw recomputation, ephemeral build, and reuse of already available + compatible state; +- retain an explicit explanation of the missing demand evidence; and +- require an explicit planning objective before using an estimated demand + distribution. + +Future uncertain-demand support may add expected-cost, percentile-cost, +worst-case, or regret objectives. Those policies must consume a typed estimate +with confidence and provenance; they are not implicit behavior of +`Predictability::Unknown`. + +### End-to-end decision order + +```text +normalize query and data workload + -> derive demand, time-scope, and data evidence + -> enumerate semantic plan alternatives + -> enumerate legal execution contracts and state lifecycles + -> validate summary capabilities and phase constraints + -> derive and check accuracy guarantees + -> normalize one-time and rate costs over an explicit horizon + -> rank legal alternatives + -> emit plan, deployments, assumptions, and rejected alternatives +``` + +## Review against the ProjectASAP glossary + +The glossary review found the following required coverage and current gaps. + +| Glossary concept | Current ASAPPlanner representation | Missing design support | +| --- | --- | --- | +| Data at rest vs continuously ingesting | Continuous ingest characteristics are available; no explicit arrival mode | Add `DataArrival`; support at-rest statistics without inventing update rate | +| Ingestion volume | Not a first-class workload input | Add evidenced volume with a time basis | +| Ingestion rate | Derived from series count and sample rate | Preserve as evidenced rate; do not conflate with query evaluation rate | +| Input cardinality | Partial `series_count` and distinct-key inputs | Define applicability to dataset, metric, columns, and time window | +| Data distribution | Small built-in enum | Preserve source/freshness; permit deployment-specific distributions later | +| Ad-hoc vs predictable | Not represented | Add predictability independently from recurrence | +| One-time vs repeated | Batch entries and fixed-interval repeating entries | Add scheduled one-time, unknown recurrence, and estimated/scheduled repetition | +| Query volume and characteristics | Fixed interval or structural consumer count | Add observation window, peak/burst and concurrency evidence where latency or capacity models require it | +| Real-time vs longitudinal | Temporal IR can carry ranges; no workload classification | Add time scope plus concrete selection; avoid inferring scope from lookback alone | +| Output cardinality | May be inferred locally; no common evidenced input | Add derived/evidenced value and provenance for costing | +| Lookback window | Represented in temporal query shapes/frontends | Establish query IR as authority and expose it to workload costing | +| CTSA pipeline | Not explicitly modeled | Keep as architectural context; planner consumes collect/store/analyze facts but does not model transmission topology in the MVP | +| CSP(F) | Cost and fidelity partly modeled | Treat scale/performance/fidelity as objectives and constraints; do not collapse fidelity into cost | + +Two terminology corrections are required in future code changes: + +1. A repeated query is not inherently a streaming-data workload. It may + repeatedly query data at rest. +2. A one-time query is not inherently stateless. A predictable one-time query + may justify prepared state, while an ephemeral summary is stateful during + its one execution. + +## Minimal complexity + +The simplest alternative is to extend `BatchEntry` with optional schedule and +classification fields and extend `RepeatingEntry` with time scope. That is a +reasonable serialization migration, but it is not a sufficient conceptual +model: it continues to make predictability and recurrence mutually exclusive +container choices, and it has no place for data arrival or state lifecycle. + +The minimum new conceptual layers are therefore: + +1. orthogonal query-demand metadata, required because glossary categories are + not one taxonomy; +2. data-workload metadata, required because ingestion does not describe query + recurrence; +3. state lifecycle as a physical alternative, required because one summary + operator can be deployed ephemerally or incrementally. + +No separate scheduler, forecasting framework, or replacement cost model is +introduced. Existing query IR, summary properties, accuracy model, and +recurrence cost types remain authoritative in their domains. + +## Alternatives and decisions + +### Encode workload class as one enum + +Rejected. Variants such as `AdHoc`, `OneShot`, and `Repeated` overlap: +predictability and recurrence are different facts, and time scope is a third. + +### Infer demand from query syntax or submitted root count + +Rejected. Syntax contains no evidence of future arrival, and several roots in +one request establish only current structural sharing. + +### Treat every summary as continuously maintained + +Rejected. It excludes ephemeral construction over data at rest and overcharges +one-time plans. It also hides deployment lifetime from explanations. + +### Treat every one-time query as raw recomputation + +Rejected. An ephemeral summary may reduce memory or network cost during one +execution, an existing summary may already answer the query, and a predictable +future query may justify preparation. + +### Fold fidelity into a scalar cost + +Rejected. Accuracy and semantic correctness are constraints checked before +ranking. A cheaper plan cannot purchase permission to violate fidelity. + +### Extend the existing recurrence profile only + +Partially accepted for implementation reuse, rejected as the whole model. +`RecurrenceProfile` is an aggregated cost context for a target. It should remain +the derived input to cost decisions, while normalized workload metadata retains +predictability, time scope, provenance, and lifecycle information needed before +and after aggregation. + +## Quality attributes and evidence + +- **Understandability:** explanations use glossary terms and show each axis + separately. Proxy: reviewers can distinguish repeated queries from continuous + ingestion in exported plan evidence. +- **Debuggability:** selected and rejected lifecycle alternatives record demand, + horizon, data statistics, and provenance. Proxy: no lifecycle decision is + explained only as a scalar cost. +- **Maintainability:** current recurrence types remain the cost authority; + normalized workload types remain the source authority. No duplicate formula + system is introduced. +- **Extensibility:** scheduled and estimated recurrence fit without changing + query semantics. Forecasting policies remain pluggable planning objectives. +- **Performance:** lifecycle enumeration expands the candidate space. The MVP + should generate only capability-compatible alternatives and deduplicate + equivalent deployments before ranking. +- **Operability:** every long-lived state has activation, retention or retirement + semantics and ownership in output. Concrete runtime APIs are future work. +- **Security and privacy:** query logs and empirical distributions may be + sensitive. Provenance must identify a source without requiring raw query-log + contents to be embedded in exported plans. + +## Acceptance and test design + +Implementation acceptance is defined by identical logical queries producing +different legal lifecycle choices under different workload contracts: + +1. **Unpredictable one-time query:** offers raw recomputation, compatible + existing state, and ephemeral build; does not justify new continuous state. +2. **Predictable scheduled one-time query:** may offer prepared state with a + bounded activation and retirement period. +3. **Repeated query over continuously ingesting data:** compares incremental + maintenance and repeated recomputation using distinct update and evaluation + rates over an explicit horizon. +4. **Repeated query over data at rest:** uses evaluation rate without inventing + maintenance updates. +5. **Real-time and longitudinal queries with the same expression:** preserve + different time selections and may receive different scan, retention, and + summary alternatives. +6. **Unknown demand:** remains unknown in explanation and cannot make a newly + created long-lived state win through assumed reuse. +7. **Mixed one-time and repeated consumers:** requires an explicit horizon and + accounts for shared build cost once. +8. **Accuracy failure:** rejects a lifecycle regardless of favorable workload + cost. + +Focused unit tests should cover normalization, invalid combinations, evidence +freshness, lifecycle capability checks, and dimensional cost arithmetic. +End-to-end tests should cover cases 1–8 through candidate selection and exported +explanations. A reviewer who did not implement the workload types should design +or review at least the unknown-demand and mixed-consumer cases; that independent +review has not occurred for this design document. + +## Risks, rollout, and exit criteria + +The implementation should roll out additively: + +1. add normalized metadata and explanations while preserving current + batch/repeating behavior; +2. derive the existing `RecurrenceProfile` from the richer model; +3. add ephemeral and existing-state alternatives; +4. add prepared and continuously maintained lifecycle selection; +5. integrate empirical demand and state catalogs only when provenance and + freshness contracts are available. + +Compatibility requires old workloads to normalize without changing their +current decisions when no new metadata is supplied. Unknown new fields must +take the documented conservative path rather than acquire optimistic defaults. + +Open decisions requiring architecture or product input: + +- whether predictable parameterized query templates count as the same repeated + query and under which equivalence relation; +- who supplies the optimization horizon and whether a deployment may define a + default for purely repeated workloads; +- which planning objective governs uncertain demand; +- how state ownership, quota, and retirement requests cross the planner/runtime + boundary; +- whether real-time versus longitudinal is supplied by the caller, derived by a + policy using `as_of` and lookback, or both with conflict diagnostics; and +- the minimum evidence freshness required before empirical workload data may + affect selection. + +The design exits draft status when these decisions have owners, the normalized +input has a compatibility plan, and acceptance cases 1–8 can be expressed in +fixtures without runtime-specific assumptions. From dd5e64c0c2f8207f636fdd099a01f915d8ee4098 Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:13:43 -0600 Subject: [PATCH 02/11] docs(design): clarify workload inputs and lifecycle scope --- docs/design_docs/asap-aware-mapping/README.md | 2 +- .../workload-demand-and-summary-lifecycle.md | 70 ++++++++++++++++--- 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/docs/design_docs/asap-aware-mapping/README.md b/docs/design_docs/asap-aware-mapping/README.md index 9e7ddaf..25d64e9 100644 --- a/docs/design_docs/asap-aware-mapping/README.md +++ b/docs/design_docs/asap-aware-mapping/README.md @@ -90,7 +90,7 @@ The design is split into focused documents: summaries and optimizations can be composed safely. - [End-to-end accuracy guarantees](end-to-end-accuracy-guarantees.md) specifies the typed guarantee IR, sketch contracts, composition rules, target checking, and fail-closed boundaries. -- [Workload demand and summary lifecycle](workload-demand-and-summary-lifecycle.md) separates +- [Query workloads, data workloads, and summary lifecycle maintenance](workload-demand-and-summary-lifecycle.md) separates query demand from data workload and defines ephemeral, prepared, shared, and continuously maintained summary-state alternatives. - [Explainability](explainability.md) describes how the planner reports available replacements diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index 0c653a1..1cb0895 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -1,4 +1,4 @@ -# Design: Workload Demand and Summary Lifecycle +# Design: Query Workloads, Data Workloads, and Summary Lifecycle Maintenance ## Audience and context @@ -53,8 +53,10 @@ same fact even though the glossary defines them on different axes. The planner receives four logically distinct inputs: 1. logical queries and their correctness and latency requirements; -2. query-workload demand, including predictability and recurrence; -3. data-workload characteristics, including ingestion and queried time scope; +2. query-workload demand, including predictability, recurrence, and queried + time scope; +3. data-workload characteristics, including arrival, volume, cardinality, and + distribution; 4. existing summaries and the lifecycle actions available to the deployment. The output is a legal physical-plan choice plus explicit state deployments. A @@ -123,9 +125,6 @@ an explicit horizon. - **What are the risks and costs?** More inputs can make planning harder to configure, forecasts may be stale, and a large lifecycle search space can increase planning cost. -- **How long will it take?** The minimum implementation can extend normalized - workload input, lifecycle candidates, and explanations incrementally. Demand - forecasting and runtime state catalogs are later integrations. - **What are the checks for success?** The acceptance cases below must produce different lifecycle alternatives and cost terms for identical query syntax under different workload contracts. @@ -228,6 +227,25 @@ struct TimeSelection { } ``` +For example, the same five-minute lookback has a different scope depending on +whether it is anchored at the current planning time or at a historical time: + +```rust +// The last five minutes: real-time. +TimeSelection { + scope: QueryTimeScope::RealTime, + lookback: Some(Duration::minutes(5)), + as_of: None, +} + +// A five-minute interval from archived data: longitudinal. +TimeSelection { + scope: QueryTimeScope::Longitudinal, + lookback: Some(Duration::minutes(5)), + as_of: Some(timestamp!("2024-01-01T12:05:00Z")), +} +``` + `lookback` is a query property already represented by temporal query nodes in some frontends. The normalized workload should reference or derive it rather than introduce a second conflicting value. @@ -242,6 +260,16 @@ enum DataArrival { Unknown, } +/// Statistical distribution of keys in the input data. +enum DataDistribution { + /// A small number of keys account for most observations. + Zipf, + /// Keys are approximately equally likely. + Uniform, + /// Observations arrive in bursts with a temporarily concentrated key set. + Bursty, +} + struct DataWorkload { arrival: DataArrival, ingestion_volume: Evidence, @@ -251,6 +279,12 @@ struct DataWorkload { } ``` +`DataDistribution` reuses the existing ASAPPlanner classification. It describes +the key-frequency shape used by summary accuracy and cost models, not whether +data arrives continuously. An unavailable or unsupported distribution is +represented by `Evidence.value = None` rather than by assuming the default +distribution. + The current `DataCharacteristics` supplies continuous-ingestion fields such as series count and samples per second. It should become one source for this model, not the authoritative definition of all data workloads. Data at rest may have @@ -265,7 +299,6 @@ struct Evidence { source: EvidenceSource, observed_at: Option, valid_for: Option, - applicability: Applicability, } ``` @@ -276,7 +309,7 @@ parameter configuration. A missing or stale value remains unknown. Output cardinality depends on input cardinality and grouping columns. The planner may derive it analytically, accept a catalog estimate, or leave it -unknown. The source and applicability must be preserved because output +unknown. The source and freshness metadata must be preserved because output cardinality affects summary size, read cost, post-processing cost, and network cost. It is not a query correctness requirement. @@ -422,7 +455,7 @@ The glossary review found the following required coverage and current gaps. | Data at rest vs continuously ingesting | Continuous ingest characteristics are available; no explicit arrival mode | Add `DataArrival`; support at-rest statistics without inventing update rate | | Ingestion volume | Not a first-class workload input | Add evidenced volume with a time basis | | Ingestion rate | Derived from series count and sample rate | Preserve as evidenced rate; do not conflate with query evaluation rate | -| Input cardinality | Partial `series_count` and distinct-key inputs | Define applicability to dataset, metric, columns, and time window | +| Input cardinality | Partial `series_count` and distinct-key inputs | Associate each estimate with its dataset, metric, columns, and observation window | | Data distribution | Small built-in enum | Preserve source/freshness; permit deployment-specific distributions later | | Ad-hoc vs predictable | Not represented | Add predictability independently from recurrence | | One-time vs repeated | Batch entries and fixed-interval repeating entries | Add scheduled one-time, unknown recurrence, and estimated/scheduled repetition | @@ -443,6 +476,25 @@ Two terminology corrections are required in future code changes: ## Minimal complexity +The minimum input model is determined by the downstream applications selected +for integration, not by a context-free notion of the fewest possible fields. +Each supported use case must contribute the workload facts that can change +plan legality, accuracy, lifecycle, or cost: + +- Time-series metric queries require queried time scope and lookback. +- Repeated dashboard queries, including an ASAPQuery integration, require + recurrence and evaluation frequency so the planner can cost reuse and + maintenance across executions. +- Batch queries over data at rest require an explicit at-rest arrival mode and + must not be assigned a fabricated ingestion rate. +- Summary techniques whose accuracy depends on the input distribution require + evidenced distribution characteristics; omitting them must produce unknown + accuracy or a conservative fallback rather than a favorable assumption. + +The initial implementation should include the union of fields required by its +committed integrations. Additional workload dimensions should be added when a +new downstream use case demonstrates that they affect a planning decision. + The simplest alternative is to extend `BatchEntry` with optional schedule and classification fields and extend `RepeatingEntry` with time scope. That is a reasonable serialization migration, but it is not a sufficient conceptual From fa56a6f6f02a3929971908aa2cd541b7a1353a7c Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:14:11 -0600 Subject: [PATCH 03/11] docs(design): define data characteristics input --- .../workload-demand-and-summary-lifecycle.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index 1cb0895..104da6a 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -270,6 +270,20 @@ enum DataDistribution { Bursty, } +/// Existing characteristics of data arriving at the ingestion layer. +struct DataCharacteristics { + /// Number of distinct active time series for this metric. + series_count: u64, + /// Sample rate per series at the SDK or agent, in hertz. + samples_per_sec_per_series: f64, + /// Encoded size of one raw metric sample, in bytes. + bytes_per_raw_sample: u32, + /// Distinct keys per flush period, if known. + distinct_keys_per_window: Option, + /// Statistical distribution of keys in the input stream. + data_distribution: DataDistribution, +} + struct DataWorkload { arrival: DataArrival, ingestion_volume: Evidence, From 6902aaa4f1b43573e10a882522efb62819963ba1 Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:20:18 -0600 Subject: [PATCH 04/11] refactor(workload): replace stale data characteristics --- crates/asap-aware-mapping/src/cost_model.rs | 4 +- crates/asap-aware-mapping/src/lib.rs | 6 +- crates/asap-aware-mapping/src/recurrence.rs | 99 +++++++++---------- crates/asap-aware-mapping/src/replacement.rs | 6 +- .../frontend-promql/tests/promql_lowering.rs | 4 +- crates/types/src/workload.rs | 87 +++++++++++----- .../workload-demand-and-summary-lifecycle.md | 28 ++---- 7 files changed, 129 insertions(+), 105 deletions(-) diff --git a/crates/asap-aware-mapping/src/cost_model.rs b/crates/asap-aware-mapping/src/cost_model.rs index 98b4f91..b1780c8 100644 --- a/crates/asap-aware-mapping/src/cost_model.rs +++ b/crates/asap-aware-mapping/src/cost_model.rs @@ -440,7 +440,7 @@ pub trait CostModel { /// how many structurally exist (`candidate.consumer_count`). See /// `crate::recurrence`'s module docs for the full design. /// - /// - `recurrence.is_empty()` (no [`RepeatingEntry`]/[`DataCharacteristics`]-derived + /// - `recurrence.is_empty()` (no [`RepeatingEntry`]/[`DataWorkload`]-derived /// metadata available): delegates to /// [`cse_share_decision`](Self::cse_share_decision), preserving /// today's structural-consumer-count behavior exactly — issue #287's @@ -456,7 +456,7 @@ pub trait CostModel { /// rates is equivalent to comparing `rate * H` for any fixed `H > 0`). /// /// [`RepeatingEntry`]: asap_types::workload::RepeatingEntry - /// [`DataCharacteristics`]: asap_types::workload::DataCharacteristics + /// [`DataWorkload`]: asap_types::workload::DataWorkload fn cse_share_decision_with_recurrence( &self, candidate: &CseCandidate, diff --git a/crates/asap-aware-mapping/src/lib.rs b/crates/asap-aware-mapping/src/lib.rs index d8adda7..613d506 100644 --- a/crates/asap-aware-mapping/src/lib.rs +++ b/crates/asap-aware-mapping/src/lib.rs @@ -204,9 +204,9 @@ pub use explanation::{ }; pub use grouping::{has_subpopulations, HydraGroupingStrategy}; pub use recurrence::{ - evaluation_rate_of, total_cost, update_rate_from_data_characteristics, CostRate, - EvaluationRate, Horizon, RecurrenceCostExplanation, RecurrenceError, RecurrenceProfile, - RootRecurrence, UpdateRate, + evaluation_rate_of, total_cost, update_rate_from_data_workload, CostRate, EvaluationRate, + Horizon, RecurrenceCostExplanation, RecurrenceError, RecurrenceProfile, RootRecurrence, + UpdateRate, }; pub use replacement::{ default_strategies, default_strategies_with, search_workload, search_workload_with, diff --git a/crates/asap-aware-mapping/src/recurrence.rs b/crates/asap-aware-mapping/src/recurrence.rs index 2fdc9b4..8b38058 100644 --- a/crates/asap-aware-mapping/src/recurrence.rs +++ b/crates/asap-aware-mapping/src/recurrence.rs @@ -2,7 +2,7 @@ //! //! ASAPPlanner already models recurring-workload metadata //! ([`asap_types::workload::RepeatingEntry`]) and ingest-rate metadata -//! ([`asap_types::workload::DataCharacteristics`]), but until this module +//! ([`asap_types::workload::DataWorkload`]), but until this module //! neither reached [`CostModel`]'s CSE share-vs-recompute decision //! ([`CostModel::cse_share_decision`]): that decision only ever compared a //! *structural* consumer count (how many workload locations reference a @@ -79,12 +79,10 @@ //! for a whole workload). A one-shot ([`asap_types::workload::BatchEntry`]) //! consumer contributes to [`RecurrenceProfile::one_shot_consumers`] //! instead, never to this rate. -//! - [`UpdateRate`]: derived from workload-level -//! [`asap_types::workload::DataCharacteristics`] via -//! [`update_rate_from_data_characteristics`] (`series_count * -//! samples_per_sec_per_series`) — a deployment with a more precise -//! per-target ingest measurement should compute its own `UpdateRate` -//! instead of relying on this proxy. +//! - [`UpdateRate`]: read from workload-level +//! [`asap_types::workload::DataWorkload::ingestion_rate`] via +//! [`update_rate_from_data_workload`]. Missing evidence remains unknown; +//! data at rest is not assigned a fabricated update rate. //! - `maintenance_cost_per_update` / `summary_read_cost` / //! `raw_recompute_cost`: [`CostModel`] hooks (defaults documented on the //! trait itself, in `cost_model.rs`) — illustrative placeholders, like @@ -95,7 +93,7 @@ //! //! [`RecurrenceProfile::is_empty`] is `true` exactly when a caller supplied //! no [`RepeatingEntry`](asap_types::workload::RepeatingEntry)/ -//! [`DataCharacteristics`](asap_types::workload::DataCharacteristics)-derived +//! [`DataWorkload`](asap_types::workload::DataWorkload)-derived //! information at all (no evaluation rate, no update rate, zero recorded //! one-shot consumers — [`RecurrenceProfile::EMPTY`], its `Default`). //! [`CostModel::cse_share_decision_with_recurrence`]'s default body checks @@ -106,7 +104,7 @@ use std::fmt; -use asap_types::workload::{DataCharacteristics, RepetitionInterval}; +use asap_types::workload::{DataWorkload, RepetitionInterval}; use crate::cost_model::{Cost, CostModel, CseCandidate, ShareDecision}; @@ -114,8 +112,8 @@ use crate::cost_model::{Cost, CostModel, CseCandidate, ShareDecision}; /// How often the *raw* data underlying a maintained summary changes — /// ingest/update events per second (Hz). See the module docs' provenance -/// table: normally derived from [`DataCharacteristics`] via -/// [`update_rate_from_data_characteristics`]. +/// table: normally read from [`DataWorkload::ingestion_rate`] via +/// [`update_rate_from_data_workload`]. #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] pub struct UpdateRate(pub f64); @@ -234,7 +232,7 @@ pub enum RecurrenceError { /// discipline [`evaluation_rate_of`] applies to each [`RepetitionInterval`], /// applied at every point an `UpdateRate` enters a [`RecurrenceProfile`] /// ([`RecurrenceProfile::with_update_rate`], -/// [`update_rate_from_data_characteristics`], +/// [`update_rate_from_data_workload`], /// [`crate::replacement::PlanSpace::recurrence_profiles`]'s own parameter) /// *and*, as a backstop that can't be bypassed by constructing a /// `RecurrenceProfile` via its public fields directly, inside [`decide`] @@ -284,24 +282,17 @@ where Ok(any.then_some(EvaluationRate(total_hz))) } -/// Derive an [`UpdateRate`] from workload-level [`DataCharacteristics`]: -/// `series_count * samples_per_sec_per_series` — the total number of raw -/// ingest samples per second across every series this characteristics -/// value describes. A proxy, not a measurement: a deployment with a more -/// precise per-target ingest rate should compute its own `UpdateRate` -/// rather than rely on this conversion. -/// -/// Validated via [`validate_update_rate`]: `samples_per_sec_per_series` is -/// caller-supplied `f64` with no type-level guarantee of being finite or -/// non-negative, so a garbage `DataCharacteristics` value (NaN, infinite, -/// or negative) is rejected here rather than silently propagating into a -/// [`RecurrenceProfile`]. -pub fn update_rate_from_data_characteristics( - dc: &DataCharacteristics, -) -> Result { - validate_update_rate(UpdateRate( - dc.series_count as f64 * dc.samples_per_sec_per_series, - )) +/// Read an [`UpdateRate`] from workload-level [`DataWorkload`] evidence. +/// Missing evidence remains `None`; a present non-finite or negative rate is +/// rejected rather than propagated into a [`RecurrenceProfile`]. +pub fn update_rate_from_data_workload( + workload: &DataWorkload, +) -> Result, RecurrenceError> { + workload + .ingestion_rate + .value + .map(|rate| validate_update_rate(UpdateRate(rate.0))) + .transpose() } // ── RecurrenceProfile ──────────────────────────────────────────────────── @@ -326,7 +317,7 @@ pub struct RecurrenceProfile { pub one_shot_consumers: usize, /// The ingest/update rate of the raw data this target (if maintained) /// would be kept up to date against. `None` when no - /// [`DataCharacteristics`] were available. + /// [`DataWorkload::ingestion_rate`] evidence was available. pub update_rate: Option, } @@ -365,7 +356,7 @@ impl RecurrenceProfile { self } - /// Attach an ingest/update rate (from [`update_rate_from_data_characteristics`] + /// Attach an ingest/update rate (from [`update_rate_from_data_workload`] /// or a deployment-specific measurement). Validated via /// [`validate_update_rate`] — rejects a NaN, infinite, or negative rate /// rather than silently storing it. @@ -670,34 +661,42 @@ mod tests { assert_eq!(err, RecurrenceError::InvalidInterval(interval(0))); } - // ── update_rate_from_data_characteristics ──────────────────────────── + // ── update_rate_from_data_workload ─────────────────────────────────── #[test] - fn update_rate_from_data_characteristics_multiplies_series_by_sample_rate() { - let dc = DataCharacteristics { - series_count: 1_000, - samples_per_sec_per_series: 0.1, - bytes_per_raw_sample: 80, - distinct_keys_per_window: None, - data_distribution: Default::default(), + fn update_rate_from_data_workload_reads_ingestion_rate_evidence() { + let workload = DataWorkload { + ingestion_rate: asap_types::workload::Evidence { + value: Some(asap_types::workload::Rate(100.0)), + ..Default::default() + }, + ..Default::default() }; - let rate = update_rate_from_data_characteristics(&dc).unwrap(); + let rate = update_rate_from_data_workload(&workload).unwrap().unwrap(); assert!((rate.0 - 100.0).abs() < 1e-9); } #[test] - fn update_rate_from_data_characteristics_rejects_a_negative_sample_rate() { - let dc = DataCharacteristics { - series_count: 1_000, - samples_per_sec_per_series: -0.1, - bytes_per_raw_sample: 80, - distinct_keys_per_window: None, - data_distribution: Default::default(), + fn update_rate_from_data_workload_rejects_a_negative_rate() { + let workload = DataWorkload { + ingestion_rate: asap_types::workload::Evidence { + value: Some(asap_types::workload::Rate(-0.1)), + ..Default::default() + }, + ..Default::default() }; - let err = update_rate_from_data_characteristics(&dc).unwrap_err(); + let err = update_rate_from_data_workload(&workload).unwrap_err(); assert!(matches!(err, RecurrenceError::InvalidUpdateRate(_))); } + #[test] + fn update_rate_from_data_workload_preserves_missing_evidence() { + assert_eq!( + update_rate_from_data_workload(&DataWorkload::default()).unwrap(), + None + ); + } + // ── RecurrenceProfile ───────────────────────────────────────────────── #[test] @@ -1074,7 +1073,7 @@ mod tests { assert_eq!(explanation.recompute_total, Some(Cost(50.0))); } - /// A batch-only workload (no `DataCharacteristics`, only one-shot + /// A batch-only workload (no `DataWorkload`, only one-shot /// consumers) with the *default* `DefaultCostModel` must not /// unconditionally prefer `Share` regardless of how many one-shot /// consumers there are — issue #287 review bug 1's original repro, diff --git a/crates/asap-aware-mapping/src/replacement.rs b/crates/asap-aware-mapping/src/replacement.rs index 5c40e01..5835ada 100644 --- a/crates/asap-aware-mapping/src/replacement.rs +++ b/crates/asap-aware-mapping/src/replacement.rs @@ -2311,7 +2311,7 @@ impl PlanSpace { /// One [`RecurrenceProfile`] per discovered [`MemoGroup`] target, built by /// [`PlanSpace::recurrence_profiles`] — the "carry `RepeatingEntry.interval` -/// and relevant `DataCharacteristics` into ASAP-aware search/cost context" +/// and relevant `DataWorkload` into ASAP-aware search/cost context" /// half of issue #287. Looked up by `Rc` pointer identity, the same /// currency [`PlanSpace::group_for`]/[`GlobalSelection::for_target`] already /// use. @@ -2372,10 +2372,10 @@ impl PlanSpace { /// `update_rate` is applied uniformly to every discovered site *that /// this walk actually reached from some root* (see the "unreachable /// sites" note below): today's - /// [`asap_types::workload::DataCharacteristics`] is a single + /// [`asap_types::workload::DataWorkload`] is a single /// workload-level value (applies to every query in a `QueryWorkload`), /// not per-target, so there is no finer-grained source to attach - /// instead. `None` when no `DataCharacteristics` were available — + /// instead. `None` when no `DataWorkload` evidence was available — /// preserves "missing metadata" behavior for the update-rate term alone /// even when repeating/one-shot consumer information is present. /// diff --git a/crates/frontend-promql/tests/promql_lowering.rs b/crates/frontend-promql/tests/promql_lowering.rs index 192118d..b872508 100644 --- a/crates/frontend-promql/tests/promql_lowering.rs +++ b/crates/frontend-promql/tests/promql_lowering.rs @@ -769,7 +769,7 @@ fn batch_lowers_each_entry_and_reads_per_query_accuracy() { }, ]), repeating_queries: None, - data_characteristics: None, + data_workload: None, }; let results = lower_promql_batch(&workload); assert_eq!(results.len(), 2); @@ -787,7 +787,7 @@ fn batch_rejects_non_promql_language() { requirements: None, }]), repeating_queries: None, - data_characteristics: None, + data_workload: None, }; let results = lower_promql_batch(&workload); assert_eq!(results.len(), 1); diff --git a/crates/types/src/workload.rs b/crates/types/src/workload.rs index 9fbc98f..7a444fd 100644 --- a/crates/types/src/workload.rs +++ b/crates/types/src/workload.rs @@ -50,8 +50,8 @@ pub struct BatchEntry { pub requirements: Option, } -/// One entry in a repeating (streaming) workload: a query that fires -/// every `interval` milliseconds. +/// One query that fires every `interval` milliseconds. Its recurrence does +/// not imply that the queried data is continuously ingesting. #[derive(Debug, Clone)] pub struct RepeatingEntry { pub query: Query, @@ -60,7 +60,18 @@ pub struct RepeatingEntry { pub requirements: Option, } -// ── Data characteristics ────────────────────────────────────────────────────── +// ── Data workload ───────────────────────────────────────────────────────────── + +/// Whether the data queried by this workload is static, still arriving, or a +/// mixture of both. This is independent of whether queries repeat. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum DataArrival { + AtRest, + ContinuouslyIngesting, + Mixed, + #[default] + Unknown, +} /// Statistical distribution of keys in the incoming data stream. #[derive(Debug, Clone, Default, PartialEq, Eq)] @@ -78,23 +89,51 @@ pub enum DataDistribution { Bursty, } -/// Characteristics of the data arriving at the ingestion layer. -/// Used by the cost model and sketch-parameter binder to size sketches -/// and estimate transmission cost without running the query. -#[derive(Debug, Clone)] -pub struct DataCharacteristics { - /// Number of distinct active time series for this metric. - pub series_count: u64, - /// Sample rate per series at the SDK / agent (Hz). - pub samples_per_sec_per_series: f64, - /// Wire size of one raw OTLP metric data point after protobuf encoding - /// (bytes). Typical range: 50–200 bytes. - pub bytes_per_raw_sample: u32, - /// Known distinct key values per flush period for frequency / cardinality - /// sketches. `None` → inferred analytically from inserts and distribution. - pub distinct_keys_per_window: Option, - /// Statistical distribution of keys in the stream. - pub data_distribution: DataDistribution, +/// Where an empirical workload value came from. +#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] +pub enum EvidenceSource { + Declared, + Observed, + Derived, + #[default] + Unknown, +} + +/// A workload value together with the provenance and freshness needed to +/// decide whether it is safe to use. Times and durations are milliseconds. +#[derive(Debug, Clone, PartialEq)] +pub struct Evidence { + pub value: Option, + pub source: EvidenceSource, + pub observed_at_ms: Option, + pub valid_for_ms: Option, +} + +impl Default for Evidence { + fn default() -> Self { + Self { + value: None, + source: EvidenceSource::Unknown, + observed_at_ms: None, + valid_for_ms: None, + } + } +} + +/// Queries per second, samples per second, or another rate whose unit is +/// established by the field that contains it. +#[derive(Debug, Clone, Copy, PartialEq)] +pub struct Rate(pub f64); + +/// Workload-level facts about the data being queried. Unlike the former +/// ingestion-only `DataCharacteristics`, this also represents data at rest. +#[derive(Debug, Clone, Default, PartialEq)] +pub struct DataWorkload { + pub arrival: DataArrival, + pub ingestion_volume: Evidence, + pub ingestion_rate: Evidence, + pub input_cardinality: Evidence, + pub distribution: Evidence, } // ── Top-level workload ──────────────────────────────────────────────────────── @@ -111,9 +150,9 @@ pub struct QueryWorkload { pub language: QueryLanguage, /// One-shot queries executed together as a batch. pub query_batch: Option>, - /// Queries that repeat on a fixed interval (streaming / continuous). + /// Queries that repeat on a fixed interval. pub repeating_queries: Option>, - /// Workload-level data characteristics used for sketch sizing and cost - /// estimation. Applies to all queries in this workload. - pub data_characteristics: Option, + /// Workload-level data facts used for accuracy and cost estimation. + /// Applies to all queries in this workload. + pub data_workload: Option, } diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index 104da6a..53d4af5 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -104,7 +104,6 @@ an explicit horizon. - Defining a sketch runtime or state-storage protocol. - Choosing a concrete forecasting algorithm for uncertain demand. - Changing accuracy targets or guarantee algebra. -- Implementing the proposed public types in this documentation-only PR. ## Heilmeier questions @@ -270,20 +269,6 @@ enum DataDistribution { Bursty, } -/// Existing characteristics of data arriving at the ingestion layer. -struct DataCharacteristics { - /// Number of distinct active time series for this metric. - series_count: u64, - /// Sample rate per series at the SDK or agent, in hertz. - samples_per_sec_per_series: f64, - /// Encoded size of one raw metric sample, in bytes. - bytes_per_raw_sample: u32, - /// Distinct keys per flush period, if known. - distinct_keys_per_window: Option, - /// Statistical distribution of keys in the input stream. - data_distribution: DataDistribution, -} - struct DataWorkload { arrival: DataArrival, ingestion_volume: Evidence, @@ -299,11 +284,12 @@ data arrives continuously. An unavailable or unsupported distribution is represented by `Evidence.value = None` rather than by assuming the default distribution. -The current `DataCharacteristics` supplies continuous-ingestion fields such as -series count and samples per second. It should become one source for this model, -not the authoritative definition of all data workloads. Data at rest may have -row count and scan statistics without a nonzero ingestion rate. Unknown arrival -must not be interpreted as continuously ingesting or at rest. +The former `DataCharacteristics` was a stale, continuous-ingestion-specific +case built around series count and samples per second. `DataWorkload` replaces +it as the normalized input rather than embedding that special case in the +general model. Data at rest may have row count and scan statistics without a +nonzero ingestion rate. Unknown arrival must not be interpreted as continuously +ingesting or at rest. Every empirical value uses an evidence wrapper conceptually containing: @@ -480,7 +466,7 @@ The glossary review found the following required coverage and current gaps. | CTSA pipeline | Not explicitly modeled | Keep as architectural context; planner consumes collect/store/analyze facts but does not model transmission topology in the MVP | | CSP(F) | Cost and fidelity partly modeled | Treat scale/performance/fidelity as objectives and constraints; do not collapse fidelity into cost | -Two terminology corrections are required in future code changes: +Two terminology constraints apply: 1. A repeated query is not inherently a streaming-data workload. It may repeatedly query data at rest. From d541e86811ef2f649f5e0745c2e260b1d59db04e Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:24:09 -0600 Subject: [PATCH 05/11] docs(design): use query workload terminology --- docs/design_docs/asap-aware-mapping/README.md | 4 ++-- .../workload-demand-and-summary-lifecycle.md | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/design_docs/asap-aware-mapping/README.md b/docs/design_docs/asap-aware-mapping/README.md index 25d64e9..9034fb9 100644 --- a/docs/design_docs/asap-aware-mapping/README.md +++ b/docs/design_docs/asap-aware-mapping/README.md @@ -91,8 +91,8 @@ The design is split into focused documents: - [End-to-end accuracy guarantees](end-to-end-accuracy-guarantees.md) specifies the typed guarantee IR, sketch contracts, composition rules, target checking, and fail-closed boundaries. - [Query workloads, data workloads, and summary lifecycle maintenance](workload-demand-and-summary-lifecycle.md) separates - query demand from data workload and defines ephemeral, prepared, shared, and continuously - maintained summary-state alternatives. + query-workload properties from data-workload properties and defines ephemeral, prepared, + shared, and continuously maintained summary-state alternatives. - [Explainability](explainability.md) describes how the planner reports available replacements using the same candidate space it optimizes. diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index 53d4af5..c4626c1 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -3,9 +3,10 @@ ## Audience and context This document is for ASAPPlanner designers, architects, researchers, and -developers working on workload-aware plan selection. It defines how the planner -should describe query demand, data workload, and the lifecycle of summary state. -It is a design contract, not a description of the current public Rust API. +developers working on workload-aware plan selection. It defines how the +planner should describe query workload, data workload, and the lifecycle of +summary state. It is a design contract, not a description of the current +public Rust API. The terminology follows the ProjectASAP [glossary](https://github.com/ProjectASAP/internal-docs/blob/03e1c70f5af3ae9221471898541067eee7f86338/glossary.md). @@ -66,7 +67,7 @@ the demand and data evidence used in the decision. ```text logical queries + requirements - query demand -----+ + query workload -----+ data workload ---+--> candidate plans available summaries ---+ -> semantic and accuracy legality -> lifecycle alternatives @@ -108,7 +109,7 @@ an explicit horizon. ## Heilmeier questions - **What are we trying to do?** Choose whether summary state should be built, - maintained, shared, reused, or avoided for different kinds of query demand + maintained, shared, reused, or avoided for different query workloads and data workload. - **How is it done today, and what are the limits?** The planner distinguishes one-shot counts, fixed repeating intervals, and an ingest-rate proxy. It @@ -136,8 +137,8 @@ an explicit horizon. | --- | --- | --- | | Query meaning | Pre-ASAP query IR | Workload metadata must not change semantics | | Accuracy and latency requirement | Per-query requirements | Requirements belong to the requested result | -| Query demand | Workload input | Arrival and recurrence are not inferable from syntax | -| Data workload | Workload input | Ingestion and distribution describe the data, not query demand | +| Query workload | Workload input | Arrival and recurrence are not inferable from syntax | +| Data workload | Workload input | Ingestion and distribution describe the data, not query workload | | Summary capability | Summary properties | Merge, delete, and update support constrain legal lifecycles | | State lifecycle | Physical planning decision | Lifecycle is selected, not declared by `SummaryAgg` | | Cost | Cost model and explanation | Cost consumes all inputs but does not define their meaning | From b2110c5ce4c8d5d655a5a5d07ec9737e0eb06a18 Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:26:28 -0600 Subject: [PATCH 06/11] docs(design): separate accuracy and latency requirements --- .../workload-demand-and-summary-lifecycle.md | 55 ++++++++++++++++--- 1 file changed, 46 insertions(+), 9 deletions(-) diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index c4626c1..e8a4538 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -53,9 +53,9 @@ same fact even though the glossary defines them on different axes. The planner receives four logically distinct inputs: -1. logical queries and their correctness and latency requirements; -2. query-workload demand, including predictability, recurrence, and queried - time scope; +1. logical queries, which define query semantics; +2. query workload, including per-query accuracy and latency requirements, + predictability, recurrence, and queried time scope; 3. data-workload characteristics, including arrival, volume, cardinality, and distribution; 4. existing summaries and the lifecycle actions available to the deployment. @@ -66,7 +66,7 @@ bounded period, or continuously maintained. Its cost explanation identifies the demand and data evidence used in the decision. ```text -logical queries + requirements + logical queries ---+ query workload -----+ data workload ---+--> candidate plans available summaries ---+ -> semantic and accuracy legality @@ -136,18 +136,55 @@ an explicit horizon. | Concept | Authoritative layer | Reason | | --- | --- | --- | | Query meaning | Pre-ASAP query IR | Workload metadata must not change semantics | -| Accuracy and latency requirement | Per-query requirements | Requirements belong to the requested result | +| Accuracy requirement | Query workload (per-query) | The required result fidelity may be explicit or supplied by the normalization default | +| Latency requirement | Query workload (per-query) | The optional end-to-end latency bound belongs to one query execution | | Query workload | Workload input | Arrival and recurrence are not inferable from syntax | | Data workload | Workload input | Ingestion and distribution describe the data, not query workload | | Summary capability | Summary properties | Merge, delete, and update support constrain legal lifecycles | | State lifecycle | Physical planning decision | Lifecycle is selected, not declared by `SummaryAgg` | | Cost | Cost model and explanation | Cost consumes all inputs but does not define their meaning | -### Query workload: three independent axes +### Query workload + +Accuracy and latency are separate per-query requirements within the query +workload. They constrain different planner decisions and must not be collapsed +into one SLA value: + +```rust +enum AccuracyRequirement { + /// The caller supplied the required result fidelity. + Explicit(AccuracyTarget), + /// The source omitted accuracy; normalization applies the exact default. + ImplicitExact, +} + +enum LatencyRequirement { + /// Maximum permitted end-to-end latency for one query execution. + ExplicitMax(Duration), + /// The caller supplied no latency bound. + Unspecified, +} + +struct QueryRequirements { + accuracy: AccuracyRequirement, + latency: LatencyRequirement, +} +``` + +An omitted accuracy field is not an unknown accuracy target and does not permit +arbitrary approximation: the current normalization policy makes it +`ImplicitExact`. Keeping that variant distinct from `Explicit(Exact)` preserves +whether the caller chose exactness or inherited the default. An unspecified +latency requirement imposes no latency constraint; it is not a zero-duration +bound or evidence that every latency is acceptable. Accuracy is checked as a +legality constraint, while latency is used to reject plans that cannot meet the +bound. + +#### Classification axes The glossary classifications must be modeled independently. -#### Predictability +##### Predictability ```rust enum Predictability { @@ -167,7 +204,7 @@ known in advance. The glossary currently places exploratory/ad-hoc queries in the one-time category, so the MVP should accept `AdHoc + OneTime` and reserve other combinations until a concrete use case establishes their semantics. -#### Recurrence +##### Recurrence ```rust enum QueryRecurrence { @@ -203,7 +240,7 @@ than estimates and do not need fabricated confidence. The MVP may cost only invocation count and evaluation rate, but it must preserve unsupported volume characteristics for explanation rather than silently discarding them. -#### Queried time scope +##### Queried time scope ```rust enum QueryTimeScope { From 266e95205729914e7525476ac6000f8de7ce8a5b Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:28:04 -0600 Subject: [PATCH 07/11] docs(design): define estimated query demand --- .../workload-demand-and-summary-lifecycle.md | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index e8a4538..195577b 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -223,6 +223,36 @@ enum RepeatedDemand { Scheduled(Vec), EstimatedRate(DemandEstimate), } + +struct DemandEstimate { + /// Time range over which the demand was measured or forecast. + observation_window: ObservationWindow, + /// Expected demand, expressed in exactly one form. + expected: ExpectedDemand, + /// Highest expected invocation rate within the observation window. + peak_rate: Option, + /// Highest expected number of simultaneously executing invocations. + max_concurrency: Option, + /// Confidence in this estimate, in the inclusive range [0.0, 1.0]. + confidence: Confidence, + source: EvidenceSource, + observed_at: Option, + valid_for: Option, +} + +enum ExpectedDemand { + /// Expected total invocations over `observation_window`. + InvocationCount(u64), + /// Expected average invocations per second over `observation_window`. + AverageRate(Rate), +} + +struct ObservationWindow { + start: Timestamp, + end: Timestamp, +} + +struct Confidence(f64); ``` One-time means no recurrence is expected for that workload entry. Several @@ -233,12 +263,14 @@ equivalence policy before their executions count as the same query. Query-workload volume is more than an average rate. Cost and latency can differ for the same total request count when requests arrive in bursts or concurrently. -An empirical `DemandEstimate` should therefore be able to carry an observation -window, expected invocation count or rate, peak rate, concurrency, confidence, -and provenance. Fixed intervals and explicit schedules are declarations rather -than estimates and do not need fabricated confidence. The MVP may cost only -invocation count and evaluation rate, but it must preserve unsupported volume -characteristics for explanation rather than silently discarding them. +`ExpectedDemand` makes invocation count and average rate alternative +representations, preventing conflicting values in one estimate. The observation +window must be non-empty, rates must be finite and non-negative, and +`Confidence` must be between zero and one. Fixed intervals and explicit +schedules are declarations rather than estimates and do not need fabricated +confidence. The MVP may cost only invocation count and evaluation rate, but it +must preserve unsupported volume characteristics for explanation rather than +silently discarding them. ##### Queried time scope From 549b5b7885998351c238b9ebdf98c9e8fe276a66 Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:28:51 -0600 Subject: [PATCH 08/11] docs(design): distinguish time scope from response latency --- .../workload-demand-and-summary-lifecycle.md | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index 195577b..3b73e40 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -137,7 +137,7 @@ an explicit horizon. | --- | --- | --- | | Query meaning | Pre-ASAP query IR | Workload metadata must not change semantics | | Accuracy requirement | Query workload (per-query) | The required result fidelity may be explicit or supplied by the normalization default | -| Latency requirement | Query workload (per-query) | The optional end-to-end latency bound belongs to one query execution | +| Response-latency requirement | Query workload (per-query) | The optional end-to-end response-time bound belongs to one query execution | | Query workload | Workload input | Arrival and recurrence are not inferable from syntax | | Data workload | Workload input | Ingestion and distribution describe the data, not query workload | | Summary capability | Summary properties | Merge, delete, and update support constrain legal lifecycles | @@ -167,7 +167,7 @@ enum LatencyRequirement { struct QueryRequirements { accuracy: AccuracyRequirement, - latency: LatencyRequirement, + response_latency: LatencyRequirement, } ``` @@ -175,10 +175,10 @@ An omitted accuracy field is not an unknown accuracy target and does not permit arbitrary approximation: the current normalization policy makes it `ImplicitExact`. Keeping that variant distinct from `Explicit(Exact)` preserves whether the caller chose exactness or inherited the default. An unspecified -latency requirement imposes no latency constraint; it is not a zero-duration -bound or evidence that every latency is acceptable. Accuracy is checked as a -legality constraint, while latency is used to reject plans that cannot meet the -bound. +response-latency requirement imposes no response-time constraint; it is not a +zero-duration bound or evidence that every latency is acceptable. Accuracy is +checked as a legality constraint, while response latency is used to reject +plans that cannot meet the bound. #### Classification axes @@ -283,6 +283,12 @@ enum QueryTimeScope { } ``` +`QueryTimeScope` is not a response-latency requirement. It classifies the event +time of the data selected by the query; `LatencyRequirement` constrains the +wall-clock time allowed to produce the result. They are independent: a +longitudinal query over archived data may require a 100 ms response, while a +real-time query over the latest data may permit a 30 second response. + This classification is not derived only from a numeric lookback. A five-minute lookback over recent data is real-time; the same duration over archived data is not. Planning input should therefore carry the classification and the concrete From af47103419f4a972fec72a20f33e4fc68d294eb2 Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:34:37 -0600 Subject: [PATCH 09/11] docs(design): define optimization horizon --- .../workload-demand-and-summary-lifecycle.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index 3b73e40..1ef0300 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -460,6 +460,25 @@ that "reuse existing" and "create new" remain distinguishable alternatives. ### Cost over a horizon +`H` is the optimization horizon: the future wall-clock duration over which the +planner compares one-time and recurring costs. The existing cost model +represents it in seconds: + +```rust +/// A finite, strictly positive optimization duration, in seconds. +struct Horizon(f64); +``` + +The horizon is not the query lookback, the queried time scope, or the response +latency bound. It answers only "over how much future execution time should +these alternatives be costed?" All alternatives in one decision must use the +same `H`. `reads(H)` is the number of query evaluations expected or scheduled +within that horizon; for a fixed evaluation rate it is +`H * evaluation_rate`, plus any separately modeled one-time invocations. +Who supplies `H`, and whether a deployment may default it, remains an explicit +architecture decision below. If no horizon is available, the planner must not +compare a one-time cost with a rate-valued cost. + For a stateful incremental alternative over horizon `H`: ```text From 1b2ef0c2000773319fe79f34abfce95a6740542b Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:36:03 -0600 Subject: [PATCH 10/11] docs(design): standardize summary retirement cost --- .../workload-demand-and-summary-lifecycle.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index 1ef0300..c033ed2 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -498,9 +498,14 @@ total(H) = reads(H) * raw_recompute_cost For an ephemeral summary: ```text -total = invocations * (build_cost + summary_read_cost + disposal_cost) +total = invocations * (build_cost + summary_read_cost + retirement_cost) ``` +`retirement_cost` consistently means the one-time cost of ending a summary +state lifecycle, including deallocation or other cleanup. For ephemeral state, +retirement happens immediately after each invocation; for prepared, shared, or +continuously maintained state, it happens when that deployment is retired. + For prepared state, update and retention terms apply only between activation and retirement. Existing state does not pay a new build cost, but its catalog provenance must establish that assumption. From 2c9d4a3748e4183fe2c2e944bc8efa2c93cba84d Mon Sep 17 00:00:00 2001 From: zz_y Date: Sat, 29 Aug 2026 06:37:14 -0600 Subject: [PATCH 11/11] docs(design): move decision flow to overview --- .../workload-demand-and-summary-lifecycle.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md index c033ed2..fef2aff 100644 --- a/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md +++ b/docs/design_docs/asap-aware-mapping/workload-demand-and-summary-lifecycle.md @@ -82,6 +82,20 @@ preparing state in advance with building or recomputing at execution time. For repeated queries, it may amortize build and maintenance cost across reads over an explicit horizon. +### End-to-end decision order + +```text +normalize query and data workloads + -> derive recurrence, time-scope, and data evidence + -> enumerate semantic plan alternatives + -> enumerate legal execution contracts and state lifecycles + -> validate summary capabilities and phase constraints + -> derive and check accuracy guarantees + -> normalize one-time and rate costs over an explicit horizon + -> rank legal alternatives + -> emit plan, deployments, assumptions, and rejected alternatives +``` + ## Goals and non-goals ### Goals @@ -532,20 +546,6 @@ worst-case, or regret objectives. Those policies must consume a typed estimate with confidence and provenance; they are not implicit behavior of `Predictability::Unknown`. -### End-to-end decision order - -```text -normalize query and data workload - -> derive demand, time-scope, and data evidence - -> enumerate semantic plan alternatives - -> enumerate legal execution contracts and state lifecycles - -> validate summary capabilities and phase constraints - -> derive and check accuracy guarantees - -> normalize one-time and rate costs over an explicit horizon - -> rank legal alternatives - -> emit plan, deployments, assumptions, and rejected alternatives -``` - ## Review against the ProjectASAP glossary The glossary review found the following required coverage and current gaps.