use std::time::Duration;
use crate::error::ClusterError;
pub const SEQUENCER_GROUP_ID: u64 = 0xFFFF_FFFF_0000_0001;
#[derive(Debug, Clone)]
pub struct SequencerConfig {
pub epoch_duration: Duration,
pub inbox_capacity: usize,
pub vshard_channel_depth: usize,
pub max_plans_bytes_per_txn: usize,
pub max_participating_vshards_per_txn: usize,
pub max_txns_per_epoch: usize,
pub max_bytes_per_epoch: usize,
pub tenant_inbox_quota: usize,
pub max_dependent_read_bytes_per_txn: usize,
pub max_dependent_read_passives_per_txn: usize,
}
impl Default for SequencerConfig {
fn default() -> Self {
let inbox_capacity = 10_000;
Self {
epoch_duration: Duration::from_millis(20),
inbox_capacity,
vshard_channel_depth: 512,
max_plans_bytes_per_txn: 1 << 20,
max_participating_vshards_per_txn: 64,
max_txns_per_epoch: 1_024,
max_bytes_per_epoch: 16 << 20,
tenant_inbox_quota: (inbox_capacity / 8).max(1),
max_dependent_read_bytes_per_txn: 4 << 20,
max_dependent_read_passives_per_txn: 16,
}
}
}
impl SequencerConfig {
pub fn validate(&self) -> Result<(), ClusterError> {
let caps: &[(&'static str, usize)] = &[
("inbox_capacity", self.inbox_capacity),
("vshard_channel_depth", self.vshard_channel_depth),
("max_plans_bytes_per_txn", self.max_plans_bytes_per_txn),
(
"max_participating_vshards_per_txn",
self.max_participating_vshards_per_txn,
),
("max_txns_per_epoch", self.max_txns_per_epoch),
("max_bytes_per_epoch", self.max_bytes_per_epoch),
("tenant_inbox_quota", self.tenant_inbox_quota),
(
"max_dependent_read_bytes_per_txn",
self.max_dependent_read_bytes_per_txn,
),
(
"max_dependent_read_passives_per_txn",
self.max_dependent_read_passives_per_txn,
),
];
for (name, value) in caps {
if *value < 1 {
return Err(ClusterError::Config {
detail: format!("SequencerConfig.{name} must be >= 1, got {value}"),
});
}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_values_are_sane() {
let cfg = SequencerConfig::default();
assert_eq!(cfg.epoch_duration, Duration::from_millis(20));
assert_eq!(cfg.inbox_capacity, 10_000);
assert_eq!(cfg.vshard_channel_depth, 512);
assert_eq!(cfg.max_plans_bytes_per_txn, 1 << 20);
assert_eq!(cfg.max_participating_vshards_per_txn, 64);
assert_eq!(cfg.max_txns_per_epoch, 1_024);
assert_eq!(cfg.max_bytes_per_epoch, 16 << 20);
assert_eq!(cfg.tenant_inbox_quota, 10_000 / 8);
assert_eq!(cfg.max_dependent_read_bytes_per_txn, 4 << 20);
assert_eq!(cfg.max_dependent_read_passives_per_txn, 16);
}
#[test]
fn sequencer_group_id_not_zero_and_not_small() {
assert_ne!(SEQUENCER_GROUP_ID, 0);
let id: u64 = SEQUENCER_GROUP_ID;
assert!(id > 0xFFFF_0000);
}
#[test]
fn validate_accepts_default_config() {
SequencerConfig::default()
.validate()
.expect("default should be valid");
}
#[test]
fn validate_rejects_zero_cap() {
let cfg = SequencerConfig {
max_txns_per_epoch: 0,
..SequencerConfig::default()
};
assert!(cfg.validate().is_err());
}
#[test]
fn tenant_quota_floor_is_one_for_tiny_capacity() {
let capacity: usize = 1;
let quota = (capacity / 8).max(1);
let cfg = SequencerConfig {
inbox_capacity: capacity,
tenant_inbox_quota: quota,
..SequencerConfig::default()
};
assert_eq!(cfg.tenant_inbox_quota, 1);
}
}