Skip to main content

azoth_core/config/
canonical.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4/// Configuration for read connection pooling
5///
6/// When enabled, maintains a pool of read-only connections/transactions
7/// for concurrent read access without blocking writes.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ReadPoolConfig {
10    /// Whether pooling is enabled (default: false, opt-in)
11    #[serde(default)]
12    pub enabled: bool,
13
14    /// Number of read connections/slots in the pool (default: 4)
15    ///
16    /// For LMDB: controls concurrent read transaction slots
17    /// For SQLite: controls number of read-only connections
18    #[serde(default = "default_pool_size")]
19    pub pool_size: usize,
20
21    /// Timeout in milliseconds when acquiring a pooled connection (default: 5000)
22    ///
23    /// If no connection is available within this time, an error is returned.
24    #[serde(default = "default_acquire_timeout")]
25    pub acquire_timeout_ms: u64,
26}
27
28impl Default for ReadPoolConfig {
29    fn default() -> Self {
30        Self {
31            enabled: false,
32            pool_size: default_pool_size(),
33            acquire_timeout_ms: default_acquire_timeout(),
34        }
35    }
36}
37
38impl ReadPoolConfig {
39    /// Create a new enabled read pool configuration
40    pub fn enabled(pool_size: usize) -> Self {
41        Self {
42            enabled: true,
43            pool_size,
44            acquire_timeout_ms: default_acquire_timeout(),
45        }
46    }
47
48    /// Set the acquire timeout
49    pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
50        self.acquire_timeout_ms = timeout_ms;
51        self
52    }
53}
54
55fn default_pool_size() -> usize {
56    4
57}
58
59fn default_acquire_timeout() -> u64 {
60    5000
61}
62
63/// Configuration for canonical store
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct CanonicalConfig {
66    /// Path to the canonical store directory
67    pub path: PathBuf,
68
69    /// Maximum map size for LMDB (in bytes)
70    /// Default: 10GB
71    #[serde(default = "default_map_size")]
72    pub map_size: usize,
73
74    /// Sync mode for durability
75    #[serde(default)]
76    pub sync_mode: SyncMode,
77
78    /// Number of stripes for lock manager
79    /// Default: 256
80    #[serde(default = "default_stripe_count")]
81    pub stripe_count: usize,
82
83    /// Maximum number of readers (LMDB specific)
84    /// Default: 126
85    #[serde(default = "default_max_readers")]
86    pub max_readers: u32,
87
88    /// Use read-only transactions for preflight (default: true)
89    ///
90    /// When enabled, preflight validation uses concurrent read-only transactions
91    /// instead of write transactions, improving throughput and reducing contention.
92    #[serde(default = "default_true")]
93    pub preflight_read_only: bool,
94
95    /// Chunk size for state iteration (default: 1000)
96    ///
97    /// State iterators fetch data in chunks to maintain constant memory usage.
98    /// Larger chunks improve throughput but use more memory temporarily.
99    #[serde(default = "default_chunk_size")]
100    pub state_iter_chunk_size: usize,
101
102    /// Enable in-memory cache for preflight validation (default: true)
103    ///
104    /// When enabled, frequently accessed state keys are cached in memory during
105    /// preflight validation, reducing LMDB reads for hot keys.
106    #[serde(default = "default_true")]
107    pub preflight_cache_enabled: bool,
108
109    /// Maximum number of entries in the preflight cache (default: 10,000)
110    ///
111    /// Each entry uses approximately 120 bytes + value size.
112    /// Default of 10,000 entries ≈ 1-6 MB memory overhead.
113    #[serde(default = "default_preflight_cache_size")]
114    pub preflight_cache_size: usize,
115
116    /// Time-to-live for preflight cache entries in seconds (default: 60)
117    ///
118    /// Entries older than this will be evicted on access.
119    #[serde(default = "default_preflight_cache_ttl")]
120    pub preflight_cache_ttl_secs: u64,
121
122    /// Read pool configuration (optional, disabled by default)
123    ///
124    /// When enabled, maintains a pool of read-only connections for
125    /// concurrent read access without blocking writes.
126    #[serde(default)]
127    pub read_pool: ReadPoolConfig,
128}
129
130#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
131pub enum SyncMode {
132    /// Full durability (fsync on every commit)
133    Full,
134    /// No metadata sync (faster, usually safe)
135    #[default]
136    NoMetaSync,
137    /// No sync at all (fastest, least safe)
138    NoSync,
139}
140
141fn default_map_size() -> usize {
142    10 * 1024 * 1024 * 1024 // 10GB
143}
144
145fn default_stripe_count() -> usize {
146    256
147}
148
149fn default_max_readers() -> u32 {
150    126
151}
152
153fn default_true() -> bool {
154    true
155}
156
157fn default_chunk_size() -> usize {
158    1000
159}
160
161fn default_preflight_cache_size() -> usize {
162    10_000
163}
164
165fn default_preflight_cache_ttl() -> u64 {
166    60
167}
168
169impl CanonicalConfig {
170    pub fn new(path: PathBuf) -> Self {
171        Self {
172            path,
173            map_size: default_map_size(),
174            sync_mode: SyncMode::default(),
175            stripe_count: default_stripe_count(),
176            max_readers: default_max_readers(),
177            preflight_read_only: default_true(),
178            state_iter_chunk_size: default_chunk_size(),
179            preflight_cache_enabled: default_true(),
180            preflight_cache_size: default_preflight_cache_size(),
181            preflight_cache_ttl_secs: default_preflight_cache_ttl(),
182            read_pool: ReadPoolConfig::default(),
183        }
184    }
185
186    pub fn with_map_size(mut self, map_size: usize) -> Self {
187        self.map_size = map_size;
188        self
189    }
190
191    pub fn with_sync_mode(mut self, sync_mode: SyncMode) -> Self {
192        self.sync_mode = sync_mode;
193        self
194    }
195
196    pub fn with_stripe_count(mut self, stripe_count: usize) -> Self {
197        self.stripe_count = stripe_count;
198        self
199    }
200
201    pub fn with_preflight_cache(mut self, enabled: bool) -> Self {
202        self.preflight_cache_enabled = enabled;
203        self
204    }
205
206    pub fn with_preflight_cache_size(mut self, size: usize) -> Self {
207        self.preflight_cache_size = size;
208        self
209    }
210
211    pub fn with_preflight_cache_ttl(mut self, ttl_secs: u64) -> Self {
212        self.preflight_cache_ttl_secs = ttl_secs;
213        self
214    }
215
216    /// Configure read connection pooling
217    pub fn with_read_pool(mut self, config: ReadPoolConfig) -> Self {
218        self.read_pool = config;
219        self
220    }
221
222    /// Enable read pooling with the specified pool size
223    pub fn with_read_pool_size(mut self, pool_size: usize) -> Self {
224        self.read_pool = ReadPoolConfig::enabled(pool_size);
225        self
226    }
227}