Skip to main content

mz_cluster_controller/
strategy.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! The pure strategy interface and the strategy implementations.
11//!
12//! A strategy is two pure functions over `(observed cluster state, now)`:
13//!
14//! - [`Strategy::update_state`] returns the durable writes the strategy wants
15//!   (cut-overs, record writes/clears). The controller transacts these in the
16//!   tick's first phase.
17//! - [`Strategy::desired_replicas`] returns the replica slots the strategy
18//!   contributes to the cluster's desired set. The controller unions every
19//!   strategy's contribution in the tick's second phase.
20//!
21//! Both are pure: same inputs, same output, no I/O. The controller is the sole
22//! mutator. Strategies never touch the [`ClusterControllerCtx`]; the controller
23//! assembles their inputs by pulling through it.
24//!
25//! [`ClusterControllerCtx`]: crate::ctx::ClusterControllerCtx
26
27use mz_repr::Timestamp;
28
29use crate::ctx::{ClusterState, ReplicaShape, StateWrite};
30
31/// A replica slot a strategy desires this tick. The reconcile kernel unions
32/// slots across strategies and matches them by [`ReplicaShape`] against the
33/// actual replica set.
34#[derive(Clone, Debug)]
35pub struct DesiredReplica {
36    pub shape: ReplicaShape,
37}
38
39/// One cluster-autoscaling strategy: a pair of pure functions the controller
40/// runs each tick. See the module docs.
41///
42/// `Send + Sync` so the controller (which holds a set of boxed strategies) can
43/// run on its own task.
44pub trait Strategy: Send + Sync {
45    /// A stable identifier used in audit attribution (which strategies desired a
46    /// create; drops carry no attribution).
47    fn name(&self) -> &'static str;
48
49    /// The durable writes this strategy wants for `state` at time `now`. The
50    /// default is no write, which suits a strategy that only ever contributes
51    /// replicas (like the baseline).
52    fn update_state(&self, _state: &ClusterState, _now: Timestamp) -> StateWrite {
53        StateWrite::default()
54    }
55
56    /// The replica slots this strategy contributes to `state`'s desired set at
57    /// time `now`.
58    fn desired_replicas(&self, state: &ClusterState, now: Timestamp) -> Vec<DesiredReplica>;
59}
60
61/// The implicit baseline strategy, always present.
62///
63/// Desires `replication_factor` replicas at the cluster's realized shape
64/// (`cluster.size` plus its AZ pool and logging). It holds the steady-state set
65/// so that the policy strategies can be purely additive. They only ever add to
66/// the baseline. With only the baseline engaged, the desired set equals the
67/// realized set, so a steady-state managed cluster reconciles to no decisions.
68#[derive(Clone, Copy, Debug, Default)]
69pub struct BaselineStrategy;
70
71/// The audit-attribution name of the baseline strategy.
72pub const BASELINE_STRATEGY_NAME: &str = "baseline";
73
74impl Strategy for BaselineStrategy {
75    fn name(&self) -> &'static str {
76        BASELINE_STRATEGY_NAME
77    }
78
79    fn desired_replicas(&self, state: &ClusterState, _now: Timestamp) -> Vec<DesiredReplica> {
80        let shape = state.realized_shape();
81        (0..state.replication_factor)
82            .map(|_| DesiredReplica {
83                shape: shape.clone(),
84            })
85            .collect()
86    }
87}