use std::time::Duration;
use crate::error::{DbError, DbResult};
#[derive(Debug, Clone)]
pub struct FixedConfig {
pub shard_count: usize,
pub shard_prefix_bits: usize,
pub grow_step: u32,
pub sync_interval: Duration,
pub sync_batch_size: u32,
pub enable_fsync: bool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum FixedWorkload {
Balanced,
Counters,
Sessions,
Financial,
}
impl FixedWorkload {
fn base(self) -> FixedConfig {
let common = FixedConfig {
shard_count: crate::config::default_shard_count(),
shard_prefix_bits: 0,
grow_step: 1_000_000,
sync_interval: Duration::from_millis(50),
sync_batch_size: 1024,
enable_fsync: false,
};
match self {
FixedWorkload::Balanced => common,
FixedWorkload::Counters => FixedConfig {
shard_count: 8,
sync_interval: Duration::from_millis(200),
sync_batch_size: 5000,
..common
},
FixedWorkload::Sessions => FixedConfig {
shard_count: 16,
grow_step: 500_000,
sync_interval: Duration::from_millis(20),
sync_batch_size: 1000,
..common
},
FixedWorkload::Financial => FixedConfig {
shard_count: 4,
grow_step: 100_000,
sync_interval: Duration::from_millis(1),
sync_batch_size: 1,
enable_fsync: true,
..common
},
}
}
}
#[bon::bon]
impl FixedConfig {
#[builder(builder_type = FixedConfigBuilder, finish_fn = build)]
pub fn for_workload(
#[builder(start_fn)] workload: FixedWorkload,
shard_count: Option<usize>,
shard_prefix_bits: Option<usize>,
grow_step: Option<u32>,
sync_interval: Option<Duration>,
sync_batch_size: Option<u32>,
enable_fsync: Option<bool>,
) -> FixedConfig {
let b = workload.base();
FixedConfig {
shard_count: shard_count.unwrap_or(b.shard_count),
shard_prefix_bits: shard_prefix_bits.unwrap_or(b.shard_prefix_bits),
grow_step: grow_step.unwrap_or(b.grow_step),
sync_interval: sync_interval.unwrap_or(b.sync_interval),
sync_batch_size: sync_batch_size.unwrap_or(b.sync_batch_size),
enable_fsync: enable_fsync.unwrap_or(b.enable_fsync),
}
}
}
impl FixedConfig {
pub fn balanced() -> FixedConfigBuilder {
Self::for_workload(FixedWorkload::Balanced)
}
pub fn counters() -> FixedConfigBuilder {
Self::for_workload(FixedWorkload::Counters)
}
pub fn sessions() -> FixedConfigBuilder {
Self::for_workload(FixedWorkload::Sessions)
}
pub fn financial() -> FixedConfigBuilder {
Self::for_workload(FixedWorkload::Financial)
}
pub fn test() -> Self {
FixedConfig::for_workload(FixedWorkload::Balanced)
.shard_count(3)
.grow_step(1_000)
.sync_interval(Duration::from_millis(10))
.sync_batch_size(100)
.build()
}
pub fn validate(&self) -> DbResult<()> {
if self.shard_count == 0 || self.shard_count > 255 {
return Err(DbError::Config("shard_count must be 1..=255"));
}
if self.grow_step == 0 {
return Err(DbError::Config("grow_step must be > 0"));
}
if self.sync_batch_size == 0 {
return Err(DbError::Config("sync_batch_size must be > 0"));
}
if self.shard_prefix_bits > u8::MAX as usize {
return Err(DbError::Config("shard_prefix_bits must be <= 255"));
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_config_is_valid() {
FixedConfig::balanced().build().validate().unwrap();
}
#[test]
fn test_test_config_is_valid() {
FixedConfig::test().validate().unwrap();
}
#[test]
fn test_invalid_shard_count() {
let mut c = FixedConfig::test();
c.shard_count = 0;
assert!(c.validate().is_err());
c.shard_count = 256;
assert!(c.validate().is_err());
}
#[test]
fn test_invalid_grow_step() {
let mut c = FixedConfig::test();
c.grow_step = 0;
assert!(c.validate().is_err());
}
#[test]
fn test_invalid_sync_batch_size() {
let mut c = FixedConfig::test();
c.sync_batch_size = 0;
assert!(c.validate().is_err());
}
#[test]
fn test_invalid_shard_prefix_bits() {
let mut c = FixedConfig::test();
c.shard_prefix_bits = 256;
assert!(c.validate().is_err());
let msg = c.validate().unwrap_err().to_string();
assert!(
msg.contains("shard_prefix_bits"),
"expected shard_prefix_bits error, got: {msg}"
);
}
#[test]
fn test_default_shard_count_at_least_one() {
let cfg = FixedConfig::balanced().build();
assert!(
cfg.shard_count >= 1,
"shard_count must be >= 1, got {}",
cfg.shard_count
);
cfg.validate().unwrap();
}
#[test]
fn fixed_counters_matches_table() {
let c = FixedConfig::counters().build();
assert_eq!(c.shard_count, 8);
assert_eq!(c.grow_step, 1_000_000);
assert_eq!(c.sync_interval, Duration::from_millis(200));
assert_eq!(c.sync_batch_size, 5000);
assert!(!c.enable_fsync);
c.validate().unwrap();
}
#[test]
fn fixed_financial_fsync_on() {
let c = FixedConfig::financial().build();
assert_eq!(c.shard_count, 4);
assert_eq!(c.grow_step, 100_000);
assert_eq!(c.sync_interval, Duration::from_millis(1));
assert_eq!(c.sync_batch_size, 1);
assert!(c.enable_fsync);
c.validate().unwrap();
}
#[test]
fn fixed_for_workload_override() {
let c = FixedConfig::for_workload(FixedWorkload::Sessions)
.shard_count(2)
.build();
assert_eq!(c.shard_count, 2);
assert_eq!(c.sync_interval, Duration::from_millis(20));
}
}