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    /// Lock acquisition timeout in milliseconds (default: 5000)
130    ///
131    /// When acquiring stripe locks for transaction preflight, if a lock
132    /// cannot be acquired within this timeout, the transaction fails with
133    /// `LockTimeout` error instead of blocking indefinitely.
134    #[serde(default = "default_lock_timeout")]
135    pub lock_timeout_ms: u64,
136}
137
138#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Default)]
139pub enum SyncMode {
140    /// Full durability (fsync on every commit)
141    Full,
142    /// No metadata sync (faster, usually safe)
143    #[default]
144    NoMetaSync,
145    /// No sync at all (fastest, least safe)
146    NoSync,
147}
148
149fn default_map_size() -> usize {
150    10 * 1024 * 1024 * 1024 // 10GB
151}
152
153fn default_stripe_count() -> usize {
154    256
155}
156
157fn default_max_readers() -> u32 {
158    126
159}
160
161fn default_true() -> bool {
162    true
163}
164
165fn default_chunk_size() -> usize {
166    1000
167}
168
169fn default_preflight_cache_size() -> usize {
170    10_000
171}
172
173fn default_preflight_cache_ttl() -> u64 {
174    60
175}
176
177fn default_lock_timeout() -> u64 {
178    5000
179}
180
181impl CanonicalConfig {
182    pub fn new(path: PathBuf) -> Self {
183        Self {
184            path,
185            map_size: default_map_size(),
186            sync_mode: SyncMode::default(),
187            stripe_count: default_stripe_count(),
188            max_readers: default_max_readers(),
189            preflight_read_only: default_true(),
190            state_iter_chunk_size: default_chunk_size(),
191            preflight_cache_enabled: default_true(),
192            preflight_cache_size: default_preflight_cache_size(),
193            preflight_cache_ttl_secs: default_preflight_cache_ttl(),
194            read_pool: ReadPoolConfig::default(),
195            lock_timeout_ms: default_lock_timeout(),
196        }
197    }
198
199    pub fn with_map_size(mut self, map_size: usize) -> Self {
200        self.map_size = map_size;
201        self
202    }
203
204    pub fn with_sync_mode(mut self, sync_mode: SyncMode) -> Self {
205        self.sync_mode = sync_mode;
206        self
207    }
208
209    pub fn with_stripe_count(mut self, stripe_count: usize) -> Self {
210        self.stripe_count = stripe_count;
211        self
212    }
213
214    pub fn with_preflight_cache(mut self, enabled: bool) -> Self {
215        self.preflight_cache_enabled = enabled;
216        self
217    }
218
219    pub fn with_preflight_cache_size(mut self, size: usize) -> Self {
220        self.preflight_cache_size = size;
221        self
222    }
223
224    pub fn with_preflight_cache_ttl(mut self, ttl_secs: u64) -> Self {
225        self.preflight_cache_ttl_secs = ttl_secs;
226        self
227    }
228
229    /// Configure read connection pooling
230    pub fn with_read_pool(mut self, config: ReadPoolConfig) -> Self {
231        self.read_pool = config;
232        self
233    }
234
235    /// Enable read pooling with the specified pool size
236    pub fn with_read_pool_size(mut self, pool_size: usize) -> Self {
237        self.read_pool = ReadPoolConfig::enabled(pool_size);
238        self
239    }
240
241    /// Set lock acquisition timeout in milliseconds
242    ///
243    /// This controls how long to wait when acquiring stripe locks for
244    /// transaction preflight. Default is 5000ms.
245    pub fn with_lock_timeout(mut self, timeout_ms: u64) -> Self {
246        self.lock_timeout_ms = timeout_ms;
247        self
248    }
249}