Skip to main content

query_planning/
execution_contracts.rs

1//! Data-only memory-control and source-chunk planning contracts.
2//!
3//! Planning sizes source chunks from these policies; the runtime enforces the
4//! device budget they carry.
5
6use crate::error::{QueryError, QueryErrorCode, Result};
7
8/// The device budget and source chunking one execution runs under. An admitted
9/// attempt's budget is its grant.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub struct MemoryControlPolicy {
12    /// Allocation cap of the attempt. The runtime's device ledger enforces it;
13    /// planning only sizes chunks against it.
14    pub device_budget_bytes: u64,
15    /// Source-byte grouping target, compared with each chunk's decoded size
16    /// estimate (`crate::chunk::decoded`). `u64::MAX` leaves the configured
17    /// target unset; source tasks derive a finite target from the admitted grant.
18    pub max_source_chunk_bytes: u64,
19    /// Row groups coalesced into one source chunk. The throughput planning
20    /// mode may raise it to the row-group cap `crate::chunk` owns.
21    pub max_row_groups_per_chunk: usize,
22    /// Attempts the engine's retry may spend splitting one chunk after an
23    /// allocation failure.
24    pub max_retry_attempts: usize,
25    /// Smallest chunk, in row groups, a retry may split down to.
26    pub min_retry_chunk_row_groups: usize,
27}
28
29impl MemoryControlPolicy {
30    /// No configured source-byte ceiling; the immutable grant sizes batches.
31    pub const DEFAULT_MAX_SOURCE_CHUNK_BYTES: u64 = u64::MAX;
32    /// One row group per chunk. The default throughput planning mode raises
33    /// this cap for local Parquet sources; the conservative mode keeps it.
34    pub const DEFAULT_MAX_ROW_GROUPS_PER_CHUNK: usize = 1;
35    /// Default retry limit for splitting a source chunk after allocation failure.
36    pub const DEFAULT_MAX_RETRY_ATTEMPTS: usize = 3;
37    /// Default minimum retry chunk of one row group.
38    pub const DEFAULT_MIN_RETRY_CHUNK_ROW_GROUPS: usize = 1;
39
40    /// Rejects a zero in any field; the error names the field in its `field`
41    /// fact. Zero is not a "disabled" value here, since each field bounds a
42    /// loop or a division in chunk planning.
43    pub fn validate(self) -> Result<()> {
44        let Self {
45            device_budget_bytes,
46            max_source_chunk_bytes,
47            max_row_groups_per_chunk,
48            max_retry_attempts,
49            min_retry_chunk_row_groups,
50        } = self;
51        validate_nonzero_policy_field("device_budget_bytes", device_budget_bytes == 0)?;
52        validate_nonzero_policy_field("max_source_chunk_bytes", max_source_chunk_bytes == 0)?;
53        validate_nonzero_policy_field("max_row_groups_per_chunk", max_row_groups_per_chunk == 0)?;
54        validate_nonzero_policy_field("max_retry_attempts", max_retry_attempts == 0)?;
55        validate_nonzero_policy_field("min_retry_chunk_row_groups", min_retry_chunk_row_groups == 0)
56    }
57}
58
59/// How source chunk planning sizes local Parquet chunks. The chosen mode and
60/// the reason for it are recorded in each chunk plan.
61///
62/// In both modes the source-byte target bounds each chunk's decoded size, and
63/// a chunk whose allocation the query cap rejects is split by row group and
64/// retried within the policy's retry limits.
65#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
66pub enum SourceChunkPlanningMode {
67    /// Chunks follow `max_row_groups_per_chunk`, one row group by default.
68    /// Each chunk is then the smallest retry unit, but per-chunk work such as
69    /// partial aggregation grows with the table's row-group count.
70    Conservative,
71    /// Raises a one-row-group cap so a chunk of a local Parquet source can hold
72    /// many row groups up to the source-byte target; `crate::chunk` owns the
73    /// row-group cap and the source conditions. A source that does not qualify
74    /// keeps conservative chunks.
75    #[default]
76    LocalParquetThroughput,
77}
78
79impl SourceChunkPlanningMode {
80    /// Stable source-planning mode label used in reports and configuration.
81    pub const fn as_str(self) -> &'static str {
82        match self {
83            Self::Conservative => "conservative",
84            Self::LocalParquetThroughput => "local_parquet_throughput",
85        }
86    }
87}
88
89impl std::fmt::Display for SourceChunkPlanningMode {
90    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91        f.write_str(self.as_str())
92    }
93}
94
95fn validate_nonzero_policy_field(field: &'static str, is_zero: bool) -> Result<()> {
96    if is_zero {
97        return Err(QueryError::new(
98            QueryErrorCode::InvalidConfiguration,
99            "query_engine.memory_policy.validate",
100            "bounded memory-control policy fields must be greater than zero",
101        )
102        .with_fact("policy", "bounded")
103        .with_fact("field", field)
104        .with_fact("reason", "zero"));
105    }
106    Ok(())
107}