Skip to main content

nodedb_types/timeseries/
config.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Tiered partition configuration.
4
5use serde::{Deserialize, Serialize};
6
7use super::partition::PartitionInterval;
8
9/// Error validating a `TieredPartitionConfig`.
10#[derive(Debug, thiserror::Error)]
11#[error("config validation: {field} — {reason}")]
12pub struct ConfigValidationError {
13    pub field: String,
14    pub reason: String,
15}
16
17/// Compression codec for archived (cold) partitions.
18#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "lowercase")]
20#[non_exhaustive]
21pub enum ArchiveCompression {
22    #[default]
23    #[serde(rename = "zstd")]
24    Zstd,
25    #[serde(rename = "lz4")]
26    Lz4,
27    #[serde(rename = "snappy")]
28    Snappy,
29}
30
31/// Full lifecycle configuration for a timeseries collection.
32///
33/// All fields have sensible defaults. Platform-aware: Origin and Lite
34/// use different defaults for memory budgets and retention.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct TieredPartitionConfig {
37    // -- Tier 0: Hot (RAM memtable) --
38    pub memtable_flush_interval_ms: u64,
39    pub memtable_max_memory_bytes: u64,
40
41    // -- Tier 1: Warm (NVMe, columnar partitions) --
42    pub partition_by: PartitionInterval,
43    pub merge_after_ms: u64,
44    pub merge_count: u32,
45
46    // -- Tier 2: Cold (S3 Parquet) --
47    pub archive_after_ms: u64,
48    pub archive_compression: ArchiveCompression,
49
50    // -- Retention --
51    pub retention_period_ms: u64,
52
53    // -- Timestamp --
54    pub timestamp_column: String,
55
56    // -- Tags --
57    pub max_tag_cardinality: u32,
58
59    // -- WAL --
60    pub wal_enabled: bool,
61
62    // -- CDC --
63    pub cdc_enabled: bool,
64
65    // -- Sync (Lite only) --
66    pub sync_resolution_ms: u64,
67    pub sync_interval_ms: u64,
68    pub retain_until_synced: bool,
69
70    // -- Battery (Lite-B mobile only) --
71    #[serde(default)]
72    pub battery_aware: bool,
73
74    // -- Burst ingestion (Lite-C) --
75    #[serde(default)]
76    pub bulk_import_threshold_rows: u64,
77
78    // -- Partition size targets (Lite-C) --
79    #[serde(default)]
80    pub partition_size_target_bytes: u64,
81
82    // -- Compaction (Lite-C) --
83    #[serde(default)]
84    pub compaction_partition_threshold: u32,
85}
86
87impl TieredPartitionConfig {
88    /// Default configuration for Origin (cloud server).
89    pub fn origin_defaults() -> Self {
90        Self {
91            memtable_flush_interval_ms: 10_000,
92            memtable_max_memory_bytes: 64 * 1024 * 1024,
93            partition_by: PartitionInterval::Auto,
94            merge_after_ms: 30 * 86_400_000,
95            merge_count: 10,
96            archive_after_ms: 0,
97            retention_period_ms: 0,
98            archive_compression: ArchiveCompression::Zstd,
99            timestamp_column: String::new(),
100            max_tag_cardinality: 100_000,
101            wal_enabled: true,
102            cdc_enabled: false,
103            sync_resolution_ms: 0,
104            sync_interval_ms: 0,
105            retain_until_synced: false,
106            battery_aware: false,
107            bulk_import_threshold_rows: 0,
108            partition_size_target_bytes: 0,
109            compaction_partition_threshold: 0,
110        }
111    }
112
113    /// Default configuration for Lite (edge device).
114    pub fn lite_defaults() -> Self {
115        Self {
116            memtable_flush_interval_ms: 30_000,
117            memtable_max_memory_bytes: 4 * 1024 * 1024,
118            partition_by: PartitionInterval::Auto,
119            merge_after_ms: 7 * 86_400_000,
120            merge_count: 4,
121            archive_after_ms: 0,
122            retention_period_ms: 7 * 86_400_000,
123            archive_compression: ArchiveCompression::Zstd,
124            timestamp_column: String::new(),
125            max_tag_cardinality: 10_000,
126            wal_enabled: true,
127            cdc_enabled: false,
128            sync_resolution_ms: 0,
129            sync_interval_ms: 30_000,
130            retain_until_synced: false,
131            battery_aware: false,
132            bulk_import_threshold_rows: 1_000_000,
133            partition_size_target_bytes: 1_024 * 1_024,
134            compaction_partition_threshold: 20,
135        }
136    }
137
138    /// Validate configuration consistency.
139    pub fn validate(&self) -> Result<(), ConfigValidationError> {
140        if self.merge_count < 2 {
141            return Err(ConfigValidationError {
142                field: "merge_count".into(),
143                reason: "must be >= 2".into(),
144            });
145        }
146        if self.retention_period_ms > 0
147            && self.archive_after_ms > 0
148            && self.retention_period_ms < self.archive_after_ms
149        {
150            return Err(ConfigValidationError {
151                field: "retention_period".into(),
152                reason: "must be >= archive_after".into(),
153            });
154        }
155        Ok(())
156    }
157}