@@ -14,6 +14,38 @@ pub struct Cfg {
1414 pub addr : Option < String > ,
1515 pub cluster : Option < ClusterConfig > ,
1616 pub metrics : Option < MetricsConfig > ,
17+ pub sqlite : Option < SqliteConfig > ,
18+ }
19+
20+ #[ derive( Debug , Clone , serde:: Deserialize ) ]
21+ pub struct SqliteConfig {
22+ /// SQLite cache size in MB for the whole process
23+ /// Default: 100 MB per shard (derived from shard count)
24+ /// Higher values improve read performance but consume more RAM
25+ pub cache_size_mb : Option < i32 > ,
26+
27+ /// SQLite busy timeout in milliseconds
28+ /// Default: 5000 ms (5 seconds)
29+ /// Increase for high-contention workloads
30+ pub busy_timeout_ms : Option < u64 > ,
31+
32+ /// SQLite synchronous mode: OFF, NORMAL, FULL
33+ /// Default: NORMAL (recommended for WAL mode)
34+ /// OFF = fastest but risk of corruption on power loss
35+ /// NORMAL = good performance with corruption safety in WAL mode
36+ /// FULL = safest but slower
37+ pub synchronous : Option < SqliteSynchronous > ,
38+
39+ /// Memory-mapped I/O size in MB for the whole process
40+ /// Default: 0 (disabled)
41+ /// Only useful for large databases that don't fit in cache
42+ /// Example: 3000 (≈3 GB total)
43+ pub mmap_size : Option < u64 > ,
44+
45+ /// Maximum number of SQLite connections per shard (pool size)
46+ /// Default: 10 (sqlx default)
47+ /// Increase for higher concurrency at the cost of RAM
48+ pub max_connections : Option < u32 > ,
1749}
1850
1951#[ derive( Debug , Clone , serde:: Deserialize ) ]
@@ -46,6 +78,24 @@ pub enum CompressionType {
4678 Brotli ,
4779}
4880
81+ #[ derive( Debug , Clone , Copy , serde:: Deserialize , PartialEq ) ]
82+ #[ serde( rename_all = "UPPERCASE" ) ]
83+ pub enum SqliteSynchronous {
84+ OFF ,
85+ NORMAL ,
86+ FULL ,
87+ }
88+
89+ impl SqliteSynchronous {
90+ pub fn as_str ( & self ) -> & ' static str {
91+ match self {
92+ SqliteSynchronous :: OFF => "OFF" ,
93+ SqliteSynchronous :: NORMAL => "NORMAL" ,
94+ SqliteSynchronous :: FULL => "FULL" ,
95+ }
96+ }
97+ }
98+
4999#[ derive( Debug , Clone , serde:: Deserialize ) ]
50100pub struct CompressionConfig {
51101 pub enabled : bool ,
@@ -110,6 +160,34 @@ impl Cfg {
110160 }
111161 }
112162
163+ // Print SQLite configuration
164+ let cache_total_mb = cfg. sqlite_cache_total_mb ( ) ;
165+ let cache_per_shard_mb = cfg. sqlite_cache_size_per_shard_mb ( ) ;
166+ let cache_per_connection_mb = cfg. sqlite_cache_size_per_connection_mb ( ) ;
167+ let busy_timeout = cfg. sqlite_busy_timeout_ms ( ) ;
168+ let synchronous = cfg. sqlite_synchronous ( ) ;
169+ let mmap_total_mb = cfg. sqlite_mmap_total_mb ( ) ;
170+ let mmap_per_shard_mb = cfg. sqlite_mmap_per_shard_mb ( ) ;
171+ let mmap_per_connection_mb = cfg. sqlite_mmap_per_connection_mb ( ) ;
172+ let pool_max_connections = cfg. sqlite_pool_max_connections ( ) ;
173+
174+ println ! ( "SQLite configuration:" ) ;
175+ println ! (
176+ " cache_size: {} MB total ({} MB per shard, {} MB per connection; floor)" ,
177+ cache_total_mb, cache_per_shard_mb, cache_per_connection_mb
178+ ) ;
179+ println ! ( " busy_timeout: {} ms" , busy_timeout) ;
180+ println ! ( " synchronous: {}" , synchronous. as_str( ) ) ;
181+ if mmap_total_mb > 0 {
182+ println ! (
183+ " mmap_size: {} MB total ({} MB per shard, {} MB per connection; floor)" ,
184+ mmap_total_mb, mmap_per_shard_mb, mmap_per_connection_mb
185+ ) ;
186+ } else {
187+ println ! ( " mmap_size: disabled" ) ;
188+ }
189+ println ! ( " pool_max_connections: {}" , pool_max_connections) ;
190+
113191 Ok ( cfg)
114192 }
115193
@@ -119,4 +197,123 @@ impl Cfg {
119197 _ => false ,
120198 }
121199 }
200+
201+ /// Get configured SQLite cache size in MB for the whole process.
202+ /// Default scales with shard count to preserve the 100 MB per-shard baseline.
203+ pub fn sqlite_cache_total_mb ( & self ) -> i32 {
204+ let default_per_shard = 100usize ;
205+ let default_total = self
206+ . num_shards
207+ . saturating_mul ( default_per_shard)
208+ . min ( i32:: MAX as usize ) as i32 ;
209+
210+ self . sqlite
211+ . as_ref ( )
212+ . and_then ( |s| s. cache_size_mb )
213+ . unwrap_or ( default_total)
214+ }
215+
216+ /// Derived SQLite cache size in MB per shard (floor division).
217+ pub fn sqlite_cache_size_per_shard_mb ( & self ) -> i32 {
218+ let total = self . sqlite_cache_total_mb ( ) ;
219+ if total <= 0 {
220+ return 0 ;
221+ }
222+
223+ let shards = self . num_shards . max ( 1 ) as i64 ;
224+ let per_shard = ( total as i64 ) / shards;
225+ per_shard. clamp ( i32:: MIN as i64 , i32:: MAX as i64 ) . max ( 0 ) as i32
226+ }
227+
228+ /// Derived SQLite cache size in MB per connection (floor division).
229+ pub fn sqlite_cache_size_per_connection_mb ( & self ) -> i32 {
230+ let total = self . sqlite_cache_total_mb ( ) ;
231+ if total <= 0 {
232+ return 0 ;
233+ }
234+
235+ let shards = self . num_shards . max ( 1 ) as i64 ;
236+ let connections = self . sqlite_pool_max_connections ( ) . max ( 1 ) as i64 ;
237+ let divisor = shards. saturating_mul ( connections) ;
238+
239+ let per_connection = ( total as i64 ) / divisor;
240+ per_connection
241+ . clamp ( i32:: MIN as i64 , i32:: MAX as i64 )
242+ . max ( 0 ) as i32
243+ }
244+
245+ /// Get SQLite busy timeout in milliseconds (default: 5000 ms = 5 seconds)
246+ pub fn sqlite_busy_timeout_ms ( & self ) -> u64 {
247+ self . sqlite
248+ . as_ref ( )
249+ . and_then ( |s| s. busy_timeout_ms )
250+ . unwrap_or ( 5000 )
251+ }
252+
253+ /// Get SQLite synchronous mode (default: NORMAL)
254+ pub fn sqlite_synchronous ( & self ) -> SqliteSynchronous {
255+ self . sqlite
256+ . as_ref ( )
257+ . and_then ( |s| s. synchronous )
258+ . unwrap_or ( SqliteSynchronous :: NORMAL )
259+ }
260+
261+ /// Get SQLite mmap size in MB (default: 0 = disabled)
262+ pub fn sqlite_mmap_total_mb ( & self ) -> u64 {
263+ self . sqlite . as_ref ( ) . and_then ( |s| s. mmap_size ) . unwrap_or ( 0 )
264+ }
265+
266+ /// Derived SQLite mmap size in MB per shard (floor division).
267+ pub fn sqlite_mmap_per_shard_mb ( & self ) -> u64 {
268+ let total = self . sqlite_mmap_total_mb ( ) ;
269+ if total == 0 {
270+ return 0 ;
271+ }
272+
273+ let shards = self . num_shards . max ( 1 ) as u64 ;
274+ total / shards
275+ }
276+
277+ /// Derived SQLite mmap size in MB per connection (floor division).
278+ pub fn sqlite_mmap_per_connection_mb ( & self ) -> u64 {
279+ let total = self . sqlite_mmap_total_mb ( ) ;
280+ if total == 0 {
281+ return 0 ;
282+ }
283+
284+ let shards = self . num_shards . max ( 1 ) as u64 ;
285+ let connections = self . sqlite_pool_max_connections ( ) . max ( 1 ) as u64 ;
286+ let divisor = shards. saturating_mul ( connections) ;
287+
288+ if divisor == 0 {
289+ return 0 ;
290+ }
291+
292+ total / divisor
293+ }
294+
295+ /// Derived SQLite mmap size in bytes per connection (floor division).
296+ pub fn sqlite_mmap_per_connection_bytes ( & self ) -> u64 {
297+ let per_connection_mb = self . sqlite_mmap_per_connection_mb ( ) ;
298+ if per_connection_mb == 0 {
299+ return 0 ;
300+ }
301+
302+ per_connection_mb. saturating_mul ( 1024 ) . saturating_mul ( 1024 )
303+ }
304+
305+ /// Maximum number of SQLite connections per shard (pool size).
306+ pub fn sqlite_pool_max_connections ( & self ) -> u32 {
307+ let default = 10 ;
308+ let configured = self
309+ . sqlite
310+ . as_ref ( )
311+ . and_then ( |s| s. max_connections )
312+ . unwrap_or ( default) ;
313+
314+ match configured {
315+ 0 => 1 ,
316+ value => value,
317+ }
318+ }
122319}
0 commit comments