Repository navigation
refactor(types, stages): every valid timing × lifetime state explicit; PhysicalRequirement, satisfies_physical, no named states - #1216
Closed
zzylol wants to merge 3 commits into
Conversation
zzylol
force-pushed
the
refactor/1200-all-valid-states
branch
from
November 5, 2026 04:22
17d13c3 to
e5edb06
Compare
zzylol
force-pushed
the
refactor/1200-all-valid-states
branch
from
November 5, 2026 17:11
e5edb06 to
9eae875
Compare
zzylol
changed the base branch from
refactor/1200-timing-lifetime
to
refactor/1200-physical-properties-off-logical-ir
November 5, 2026 17:11
PhysicalProperties stays the struct of (ExecutionTiming, Lifetime) from or arms that list their states, and one unreachable!() arm for the invalid (IngestionTime, OneEvaluation): new, readable_at, summary_properties_allowed, run_kind, node_usage, materialized_tumbling_window_chains (now Result, the error names the node), materialized_run_rate (one classification shared by computations and the MILP's context rates) and the devtools phase_label. RunKind::Shareable and ContextKind::Shared become Materialized (kappa_m) and carry no copy of the candidate's properties. The selector and select path share cost_error and materialized_chains_of; Stage 3's window_read_through uses the shared chain predicate. Table tests over every valid state (PhysicalProperties::every_valid) for each consumer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ
… satisfies_physical Replaces readable_at with the interfaces #1200 and the concepts doc define: PhysicalRequirement { reader_timing, reads_from: Evaluation } (new rejects an ingestion-time reader of a later evaluation) and satisfies_physical(producer, requirement) for alpha^phys. is_materialized moves from Lifetime to PhysicalProperties, as the doc defines it; Lifetime::is_across_evaluations, now unused, is deleted. Review fixes: the realizer checks the whole selected (timing, lifetime) after the timing pass; ErrorSelectingPlan::InvalidCandidate and materialized_chains_of are deleted (materialized_tumbling_window_chains reports the node, as plan_cost does, through cost_error); the warm start uses materialized_run_rate; shareable/shared names in Stage 3 say materialized (kappa_m); is_query_time_materialized_tumbling_window_chain says what it tests; the labels say "query time, one evaluation" and "query time, materialized across evaluations"; remaining consumers match every valid state. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
zzylol
force-pushed
the
refactor/1200-all-valid-states
branch
from
November 5, 2026 20:21
9eae875 to
d32bdc6
Compare
zzylol
force-pushed
the
refactor/1200-physical-properties-off-logical-ir
branch
from
November 5, 2026 20:21
6500daf to
5ef723d
Compare
User decisions (2026-11-05). PhysicalRequirement is
{ consumer_timing, reads_from: Evaluation, consumer_schedule:
Option<EvaluationSchedule> }; EvaluationSchedule { interval_ms, phase_ms }
is the schedule of the consumer's context (I_kappa, phi_kappa), None until
the schedule sigma. Names this stack introduces say consumer, not reader
(materialized_consumers, materialized_consumers_first).
The five named PhysicalProperties consts and not_materialized_at are
deleted; callers build PhysicalProperties::new(timing, lifetime).
InvalidPhysicalRequirement folds into InvalidPhysicalProperties
(LaterEvaluationAtIngestion). Cost reports an invalid pair through the
export's PhysicalASAPDAGValidationError::InvalidPhysicalProperties,
wrapped in AnalyticalCostError::InvalidPhysicalDag, instead of a parallel
variant. Inlined: PhysicalASAPDAGNode::physical_properties(), cost_error,
lists_every_node_of and its debug assertion; the tumbling-window-chain
predicate is private to asap-cost. Test-only helpers inlined:
PhysicalProperties::every_valid (tests enumerate the pairs new accepts),
default_physical_properties and physical_properties_at_ingestion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #1223 (which is stacked on #1214); part of #1200, tracked in #1207. Base branch:
refactor/1200-physical-properties-off-logical-ir.Merge unit. This PR merges into main only together with #1207's steps 1a–6 (#1207, Order): the #1200 stack, #1203 2a and 2b, #1201 and #1204 PR 1. Nothing of the unit merges alone.
Representation / rules / enumeration
PhysicalPropertiesstays the product of its two dimensions,struct PhysicalProperties { timing: ExecutionTiming, lifetime: Lifetime }, with private fields,new(timing, lifetime)rejecting (IngestionTime, OneEvaluation(_)), andtiming()/lifetime(). That gives 8 valid states: (QueryTime, None), (IngestionTime, None), and (QueryTime, OneEvaluation), (QueryTime, AcrossEvaluations), (IngestionTime, AcrossEvaluations), each withMemoryorDisk. This PR does not change the type. It deletes the five named consts (NOT_MATERIALIZED_AT_*,MATERIALIZED_IN_MEMORY_*): every value is built withPhysicalProperties::new(τ, ℓ). An earlier version of it replaced the struct withenum PhysicalProperties { NotKept(ExecutionTiming), Kept(KeptProperties) }, a second encoding ofLifetime× timing; that version is gone (see Deleted).PhysicalRequirement { consumer_timing, reads_from: Evaluation, consumer_schedule: Option<EvaluationSchedule> }andsatisfies_physical(&producer, &requirement)(α^phys, timing-and-lifetime table).consumer_scheduleisNoneuntil the schedule σ (Physical properties: replace Materialization with timing × lifetime (None / OneEvaluation / AcrossEvaluations) and a schedule for shareable results, with r^phys and α^phys #1200 PR 2);satisfies_physicalasserts that in debug builds. It replaces refactor(types, stages): replace Materialization with PhysicalProperties (timing, lifetime) #1214'sreadable_at(consumer_timing)with the same truth table forEvaluation::Same, which every edge reads today.Why
The user asked that every state timing × lifetime can express be handled, except the invalid ones: each consumer gives each valid state a defined behavior, with no wildcard that lumps states together. In #1214 several consumers matched
(timing, lifetime)with wildcards ((_, Lifetime::None),(IngestionTime, _)), so a state could take another state's behavior silently.What
Exhaustive matches on
(timing(), lifetime()). Each of the 8 valid states has its own arm, or an arm that lists its states. The invalid pair has one arm,unreachable!("rejected by PhysicalProperties::new"). Consumers:PhysicalProperties::new,is_materializedandsatisfies_physical; Stage 2'ssummary_properties_allowed; Stage 3'srun_kind;asap_cost::node_usage(rows per window, retained bytes, working memory,maintained);materialized_tumbling_window_chains;materialized_run_rate; the devtoolsphase_label.One shared classification.
asap_cost::execution_demand::materialized_run_rate(PhysicalProperties) -> Option<MaterializedRunRate>(PerSecondOfArrivingData,PerEvaluation,PerRequest;Nonewhen not materialized) is the one place that says how often a materialized output is computed.computationsstores it per materialized node and matches on it directly; the MILP'scontext_ratesuses it too.Interfaces that held only materialized values take the full
PhysicalProperties(user decision, 2026-11-05; no restricted type, no parallel enum):PhysicalPropertiesAssignment::set(node, properties)already stores any valid properties since refactor(types, stages): physical properties off the logical IR (OperatorNode has no timing or lifetime) #1223 (a not-materialized entry is an ordinary placement at its timing; thedebug_assertwent there).MaterializedOutputCandidate::options()keepsVec<Option<PhysicalProperties>>: each option is the assignment entry the node would get, andNoneis "no entry". That is not a second spelling of (τ, None): the not-materialized choice fixes no timing, since the timing pass places an unassigned node where its consumer runs (a sharedTimeRangeorScanread by ingestion work runs at ingestion time). ReplacingNoneby (QueryTime, None) pinned such nodes to query time and failed tworuntime_installtests (RealizerRejectedfor a requested (IngestionTime, None)), so the type stays; its documentation now says whatNonemeans.RunKind::MaterializedandContextKind::Materialized(m)carry no copy of the candidate's properties;context_rateslooks them up inphysical.candidates[m]. Aκ_mcontext whose candidate is not materialized is a defined error (SelectionMilpError::Cost), not a fallback rate.Names follow the formulation.
RunKind::Shareable→RunKind::MaterializedandContextKind::Shared→ContextKind::Materialized(κ_m, Stage 3 MILP: implement the formulation variables x, y, z, t and rows (A)–(I) as named code over explicit edges #1204: contexts index only ℓ = None candidates; a materialized candidate has onex_v). The rest of Stage 3 follows:context_of_shareable→context_of_materialized,ingestion_context_of_shareable→ingestion_context_of_materialized,shared_readers→materialized_consumers,shareables_readers_first→materialized_consumers_first,shareables_needing_exact_reach→materialized_needing_exact_reach,Scope::SharedContexts→MaterializedContexts,Run::Shared→Run::Materialized,GroupRead::shareable→materialized. Labels:"query time, shared"→"query time, one evaluation","query time, kept"→"query time, materialized across evaluations"(one sketch-plan snapshot'sdisplay_namechanges with it).Errors name their node.
materialized_tumbling_window_chainsreturnsResult<_, (PhysicalASAPNodeId, AnalyticalCostError)>, asplan_costdoes, so plan selection reports it through the existingErrorSelectingPlan::Cost { candidate, node, error }. Stage 3'swindow_read_throughmatches the chain's(timing, lifetime)itself; the cost crate's predicate stays private.Fewer error names (user decision, 2026-11-05).
PhysicalRequirement::new's (IngestionTime, Later) case isInvalidPhysicalProperties::LaterEvaluationAtIngestion(no separate requirement error type). Cost reports a node whose pair is invalid through the export's own variant,PhysicalASAPDAGValidationError::InvalidPhysicalProperties { node, reason }, wrapped asAnalyticalCostError::InvalidPhysicalDag(_), instead of a parallelAnalyticalCostError::InvalidPhysicalProperties. Error names after this PR:InvalidPhysicalProperties::{OneEvaluationAtIngestion, LaterEvaluationAtIngestion};PhysicalASAPDAGValidationError::InvalidPhysicalProperties { node, reason };AnalyticalCostError::InvalidPhysicalDag(PhysicalASAPDAGValidationError); export's edge errors as on refactor(types, stages): physical properties off the logical IR (OperatorNode has no timing or lifetime) #1223 (feat(types): an unmaterialized summary build runs at its consumer's timing; one α^phys error at export #1219 folds them into one α^phys failure).Inlined (user decision, 2026-11-05):
PhysicalASAPDAGNode::physical_properties()(callers buildPhysicalProperties::new(node.output_state.timing, node.lifetime)),select::cost_error,PhysicalPropertiesAssignment::lists_every_node_ofand the facade's debug assertion that used it,PhysicalProperties::not_materialized_at.The realizer checks the whole selected state. After the timing pass,
realize_selected_candidatescompares each selected node's realizedPhysicalPropertieswith the one Stage 3 selected and priced (before: the lifetime only), so aNoneoption the timing pass places at the other timing is an error.The warm start uses
materialized_run_ratefor aκ_mcontext's rate (per second at ingestion time, per evaluation, or per request), ascomputationsand the MILP's context rates do.Comment at Stage 2's generation site lists the valid states it does not offer, with the reasons below.
Table tests over every valid state. Each test enumerates the 10 timing × lifetime pairs and keeps those
PhysicalProperties::newaccepts (the 8 valid states), inline; there is no shared test helper. The integration-tests helpersdefault_physical_properties/physical_properties_at_ingestion(refactor(types, stages): physical properties off the logical IR (OperatorNode has no timing or lifetime) #1223) are inlined into their tests too.newproperties::tests::exactly_the_ingestion_time_one_evaluation_pairs_are_invalid(all 10 pairs: exactly the 8 succeed, the 2 invalid fail)satisfies_physicalproperties::tests::satisfies_physical_over_every_valid_state(8 states × 3 valid requirements)is_materializedproperties::tests::is_materialized_over_every_valid_statePhysicalRequirement::newproperties::tests::later_evaluation_at_ingestion_is_the_invalid_requirementcrates/types/tests/physical_export.rs::export_validation_over_every_valid_statecost_over_every_valid_statemaintainedpartial_copies_over_every_valid_statecomputations_over_every_valid_stateasap_costmaterialized_run_rate_over_every_valid_stategroups::tests::run_kind_over_every_valid_statecandidate_labels::tests::phase_label_over_every_valid_statecrates/executor/tests/every_valid_state.rsEach table test lists its expected value per state, not a copy of the implementation's match. Limits (review):
cost_over_every_valid_statechecks that each state prices finitely and that exactly theDiskstates add disk bytes, not each state's bytes; the executor table puts a requested (IngestionTime, None) build where the timing pass places it (query time) until feat(types): an unmaterialized summary build runs at its consumer's timing; one α^phys error at export #1219;summary_properties_allowedandcontext_rateshave no table test of their own.Deleted:
PhysicalProperties::{NotKept, Kept},KeptPropertiesand its variants andIN_MEMORY_*consts,PhysicalProperties::kept(), andKeptRunRate/kept_run_rateoverKeptProperties.one_evaluation_at_ingestion_is_invalid,every_other_pair_is_validanda_query_time_reader_reads_only_a_kept_ingestion_output, subsumed by the table tests.PhysicalProperties::{NOT_MATERIALIZED_AT_QUERY_TIME, NOT_MATERIALIZED_AT_INGESTION, MATERIALIZED_IN_MEMORY_FOR_ONE_EVALUATION, MATERIALIZED_IN_MEMORY_ACROSS_EVALUATIONS_AT_INGESTION, MATERIALIZED_IN_MEMORY_ACROSS_EVALUATIONS_AT_QUERY_TIME}andPhysicalProperties::not_materialized_at(refactor(types, stages): replace Materialization with PhysicalProperties (timing, lifetime) #1214, refactor(types, stages): physical properties off the logical IR (OperatorNode has no timing or lifetime) #1223).PhysicalASAPDAGNode::physical_properties()(refactor(types, stages): replace Materialization with PhysicalProperties (timing, lifetime) #1214),AnalyticalCostError::InvalidPhysicalProperties(refactor(types, stages): replace Materialization with PhysicalProperties (timing, lifetime) #1214),PhysicalPropertiesAssignment::lists_every_node_of(refactor(types, stages): physical properties off the logical IR (OperatorNode has no timing or lifetime) #1223), the integration-tests helpersdefault_physical_propertiesandphysical_properties_at_ingestion(refactor(types, stages): physical properties off the logical IR (OperatorNode has no timing or lifetime) #1223).ErrorSelectingPlan::InvalidCandidateandselect::materialized_chains_of(review: the existingErrorSelectingPlan::Costalready names the node).PhysicalProperties::readable_at(replaced byPhysicalRequirement+satisfies_physical),Lifetime::is_materialized(replaced byPhysicalProperties::is_materialized),Lifetime::is_across_evaluations(unused once each consumer matches every state).ContextRate::Fixed(1.0)arm for a not-materializedκ_mcandidate.PartialOrd/Ordderives onMediumandKeptPropertiesfrom the earlier version (needed only becauseContextKindcarried a copy of the properties; it no longer does).Mediumderives what it did on refactor(types, stages): replace Materialization with PhysicalProperties (timing, lifetime) #1214.State × consumer → behavior
ing = IngestionTime, qry = QueryTime; M = Memory, D = Disk.
satisfies_physical, same evaluation (consumer qry / ing)run_kindnode_usage,materialized_run_rate)MaintainPopulation, rejected here; accepted in #1219IngestionMaterialized, per second of arriving dataMaterialized, per secondPerContextMaterialized, per evaluationMaterialized, per evaluationMaterialized, per requestMaterialized, per requestValid states Stage 2 does not offer, and why (also in the comment at the generation site):
Diskmedium (Executor: store Disk-medium materialized views and one-evaluation results on disk #1217). The executor has no disk store; aDiskstate given to it executes in memory, while cost prices it as disk. Decided 2026-11-05: noDiskoffer until Executor: store Disk-medium materialized views and one-evaluation results on disk #1217.(IngestionTime, AcrossEvaluations) is likewise not offered for non-summary nodes: rows are not maintained at ingestion time.
Formulation notation → code
MaterializedRunRate,materialized_run_rateRunKind::Materialized(wasShareable)ContextKind::{Query, Ingestion, Materialized}(MaterializedwasShared)PhysicalRequirement { consumer_timing, reads_from: Evaluation, consumer_schedule: Option<EvaluationSchedule> }(new)EvaluationSchedule { interval_ms, phase_ms }(new;Noneeverywhere until #1200 PR 2)InvalidPhysicalProperties::{OneEvaluationAtIngestion, LaterEvaluationAtIngestion}satisfies_physical(&producer, &requirement)(wasreadable_at)PhysicalProperties::is_materialized(wasLifetime::is_materialized)Concepts not in the formulation (for the user to name or add to #1200 / #1204):
MaterializedRunRate::{PerSecondOfArrivingData, PerEvaluation, PerRequest}: how often a materialized output is computed. Physical properties: replace Materialization with timing × lifetime (None / OneEvaluation / AcrossEvaluations) and a schedule for shareable results, with r^phys and α^phys #1200 PR 2 replaces it with the schedule's rateNoneoption): not materialized, with the timing left to the timing pass's placement rule.New abstractions not in #1207 or the doc: decisions (2026-11-05)
Per #1207's interface policy; the user decided each row on 2026-11-05. #1207's steps 1a–6 are one merge unit, so an interim abstraction may stay only when a later PR of the unit deletes it.
MaterializedRunRate { PerSecondOfArrivingData, PerEvaluation, PerRequest },materialized_run_rate(p)computations, the MILP's context rates and its warm startRunKind::Materialized(wasShareable),ContextKind::Materialized(wasShared) and the κ_m renames in Stage 3Materialized;ContextKind::Materializedmatches the doc'sContext::Materialized)cost_error(node, AnalyticalCostError)toErrorSelectingPlan::Costis_query_time_materialized_tumbling_window_chainasap-cost; Stage 3 matches the pair itself)PhysicalPropertiesnamed consts,not_materialized_atPhysicalProperties::new(τ, ℓ)PhysicalASAPDAGNode::physical_properties()InvalidPhysicalRequirement,AnalyticalCostError::InvalidPhysicalPropertiesInvalidPhysicalProperties::LaterEvaluationAtIngestion;AnalyticalCostError::InvalidPhysicalDag(PhysicalASAPDAGValidationError)carries the export's variantAnalyticalCostError::InvalidPhysicalDag(PhysicalASAPDAGValidationError)PhysicalProperties::every_valid,default_physical_properties,physical_properties_at_ingestion(test-only)PhysicalPropertiesAssignment::materialized_at_ingestion_where_allowed(test-support)validate_at_ingestion/planned_data_stateforces_query_time,is_filtered_build); it replaces main'sMaterializationAssignment::with_defaultPhysicalPropertiesAssignmentand its fields onPhysicalWorkloadPlan,PlanOutput,WholePlanSelection,TumblingWindowChainExpansion;SelectedPlan;options() -> Vec<Option<_>>; Stage 2's whole-assignment renamesOperatorNodeDAGNames: consumer, not reader (user decision, 2026-11-05). The names this stack introduces say consumer (the candidate reading an input) or query (whose schedule$\mathcal T_q$ an execution context κ runs on):
consumer_timing,consumer_schedule,EvaluationSchedule,materialized_consumers,materialized_consumers_first. Existing names that later PRs of the unit delete keep their word:ReaderConstraint(deleted by #1204 PR 1),may_keep_across_evaluations_per_sole_reader_queryandMinimumQueryReaders(deleted by #1200 PR 2 / #1204 PR 1).Replaced by the defined interface in this PR:
PhysicalProperties::readable_at→PhysicalRequirement+satisfies_physical;Lifetime::is_materialized→PhysicalProperties::is_materialized.Evaluation::Laterhas no production consumer yet (every edge today reads the same evaluation); the table test covers it.Divergences from the doc's section 3:
satisfies_physicalchecks the timing-and-lifetime table only; the schedule part needs σ (#1200 PR 2).How
Consumers match on
(p.timing(), p.lifetime()). Where several consumers need the same classification (how often a materialized output runs), it lives in one function,materialized_run_rate. Readers of exported plan nodes build their pair withPhysicalProperties::new(node.output_state.timing, node.lifetime); an invalid node is reported, never skipped or relabelled.Key code interfaces added / modified
crates/types/src/physical/properties.rscrates/cost/src/execution_demand.rs,crates/cost/src/usage.rs,crates/plan-selection/src/groups.rs,crates/plan-selection/src/report.rsUsage: no new entry point.
cargo run --release -p asap-devtools --bin asap_planner -- --workload tools/dag-viewer/workloads/planner-layering/example1.json --run-until stage3prints the same Stage 2 candidates and selected plan as on #1214. The table tests drive each consumer with all 8 states.Major algorithm changes
None. Every state Stage 2 offers takes the same path as before in every consumer. Cost changes only for states no generated plan reaches:
maintainedfor (IngestionTime, AcrossEvaluations(Disk)) (#1217), and (IngestionTime, None) working memory is no longer recorded as held by a query evaluation (it never was: the old arm checked!ingestion).Before this PR
Consumers of
(timing, lifetime)used wildcards, and three places re-derived "materialized chain at query time" from raw fields.After this PR
Each consumer names every valid state; α^phys is the defined
PhysicalRequirement+satisfies_physical; the run kinds and contexts carry no copy of a candidate's properties; one function classifies how often a materialized output runs.Verification
198b9a9f1(on refactor(types, stages): physical properties off the logical IR (OperatorNode has no timing or lifetime) #12235ef723d9c):cargo fmt --all -- --checkandcargo clippy --workspace --all-targets -- -D warningsclean.cargo test -p asap-types(254),-p asap-cost(46),-p asap-physical-asap-optimization(28),-p asap-executor(297),-p asap-planner(89, 8 wall-clock ignored),-p asap-plan-selection --lib(146),-p asap-devtools(109),-p asap-integration-tests(364): 0 failed. After the consumer renames:-p asap-types --lib(144) and-p asap-plan-selection --lib(146) again, 0 failed.satisfies_physical_over_every_valid_state,is_materialized_over_every_valid_state,later_evaluation_at_ingestion_is_the_invalid_requirement;exactly_the_ingestion_time_one_evaluation_pairs_are_invalidchecks thatnewaccepts exactly 8 of the 10 pairs, which every other table test enumerates inline.sketch_plan_snapshots/fuzz-issue903-raw-retained.json'sdisplay_namechanges (labelquery time, shared→query time, one evaluation); devtools snapshot tests pass, so no plan, cost or candidate count changes.9eae875a1):UPDATE_SNAPSHOTS=1 cargo test --release -p asap-devtools --test corpus_snapshots -- --include-ignored: 9 passed,git statusclean.🤖 Generated with Claude Code
https://claude.ai/code/session_01K1M6br9PL4SDeVS1XpezKJ