1use super::impl_config_secret::seed_storage_deploy_config;
13use super::*;
14
15fn boot_cache_u64(
16 store: &crate::storage::UnifiedStore,
17 env: &HashMap<String, String>,
18 key: &str,
19 default: u64,
20) -> u64 {
21 if let Some(value) = env.get(key).and_then(|value| value.parse::<u64>().ok()) {
22 return value;
23 }
24 match store.get_config(key) {
25 Some(reddb_types::Value::UnsignedInteger(value)) => value,
26 Some(reddb_types::Value::Integer(value)) => u64::try_from(value).unwrap_or(default),
27 Some(reddb_types::Value::Text(value)) => value.parse::<u64>().unwrap_or(default),
28 _ => default,
29 }
30}
31
32pub(crate) fn view_records_to_entities(
41 table: &str,
42 records: &[crate::storage::query::unified::UnifiedRecord],
43) -> Vec<crate::storage::UnifiedEntity> {
44 use std::collections::HashMap;
45 let table_arc: std::sync::Arc<str> = std::sync::Arc::from(table);
46 let mut out = Vec::with_capacity(records.len());
47 for record in records {
48 let mut named: HashMap<String, reddb_types::Value> = HashMap::new();
49 for (name, value) in record.iter_fields() {
50 named.insert(name.to_string(), value.clone());
51 }
52 let entity = crate::storage::UnifiedEntity::new(
53 crate::storage::EntityId::new(0),
54 crate::storage::EntityKind::TableRow {
55 table: std::sync::Arc::clone(&table_arc),
56 row_id: 0,
57 },
58 crate::storage::EntityData::Row(crate::storage::RowData {
59 columns: Vec::new(),
60 named: Some(named),
61 schema: None,
62 }),
63 );
64 out.push(entity);
65 }
66 out
67}
68
69fn system_keyed_collection_contract(
70 name: &str,
71 model: crate::catalog::CollectionModel,
72) -> crate::physical::CollectionContract {
73 let now = crate::utils::now_unix_millis() as u128;
74 crate::physical::CollectionContract {
75 graph_contract: None,
76 name: name.to_string(),
77 description: None,
78 description_generated: false,
79 declared_model: model,
80 schema_mode: crate::catalog::SchemaMode::Dynamic,
81 origin: crate::physical::ContractOrigin::Implicit,
82 version: 1,
83 created_at_unix_ms: now,
84 updated_at_unix_ms: now,
85 default_ttl_ms: None,
86 vector_dimension: None,
87 vector_metric: None,
88 context_index_fields: Vec::new(),
89 declared_columns: Vec::new(),
90 table_def: None,
91 timestamps_enabled: false,
92 context_index_enabled: false,
93 metrics_raw_retention_ms: None,
94 metrics_rollup_policies: Vec::new(),
95 metrics_tenant_identity: None,
96 metrics_namespace: None,
97 append_only: false,
98 subscriptions: Vec::new(),
99 analytics_config: Vec::new(),
100 session_key: None,
101 session_gap_ms: None,
102 retention_duration_ms: None,
103 analytical_storage: None,
104
105 ai_policy: None,
106 }
107}
108
109pub(crate) fn table_row_index_fields(
110 entity: &crate::storage::unified::entity::UnifiedEntity,
111) -> Vec<(String, reddb_types::Value)> {
112 let crate::storage::EntityData::Row(row) = &entity.data else {
113 return Vec::new();
114 };
115 if let Some(named) = &row.named {
116 return named
117 .iter()
118 .map(|(name, value)| (name.clone(), value.clone()))
119 .collect();
120 }
121 if let Some(schema) = &row.schema {
122 return schema
123 .iter()
124 .zip(row.columns.iter())
125 .map(|(name, value)| (name.clone(), value.clone()))
126 .collect();
127 }
128 Vec::new()
129}
130
131impl RedDBRuntime {
132 pub fn in_memory() -> RedDBResult<Self> {
133 Self::with_options(RedDBOptions::in_memory())
134 }
135
136 pub fn flush(&self) -> RedDBResult<()> {
137 let store = self.inner.db.store();
138 let publication = store.vault_publication_lock.lock();
139 self.inner
140 .db
141 .flush_local_only()
142 .map_err(|err| RedDBError::Internal(err.to_string()))?;
143 self.index_store_ref().clear_paged_btree_overlays();
144 drop(publication);
145 if let Err(err) = self.assert_remote_write_allowed("flush") {
146 tracing::warn!(
147 target: "reddb::serverless::lease",
148 error = %err,
149 "flush: skipping remote upload — lease not held"
150 );
151 return Ok(());
152 }
153 self.publish_remote_snapshot()
154 }
155
156 pub fn lock_manager(&self) -> std::sync::Arc<crate::runtime::lock_manager::LockManager> {
160 self.inner.lock_manager.clone()
161 }
162
163 pub fn config_registry(&self) -> std::sync::Arc<crate::auth::registry::ConfigRegistry> {
165 self.inner.config_registry.clone()
166 }
167
168 pub fn query_audit(&self) -> std::sync::Arc<crate::runtime::query_audit::QueryAuditStream> {
169 self.inner.query_audit.clone()
170 }
171
172 pub fn control_events_require_persistence(&self) -> bool {
173 self.inner.control_event_config.require_persistence()
174 }
175
176 pub fn control_event_config(&self) -> crate::runtime::control_events::ControlEventConfig {
177 self.inner.control_event_config
178 }
179
180 pub fn control_event_ledger(
181 &self,
182 ) -> Arc<dyn crate::runtime::control_events::ControlEventLedger> {
183 self.inner.control_event_ledger.read().clone()
184 }
185
186 #[doc(hidden)]
187 pub fn replace_control_event_ledger_for_tests(
188 &self,
189 ledger: Arc<dyn crate::runtime::control_events::ControlEventLedger>,
190 ) {
191 *self.inner.control_event_ledger.write() = ledger;
192 }
193
194 #[inline(never)]
195 pub fn with_options(options: RedDBOptions) -> RedDBResult<Self> {
196 Self::with_pool(options, ConnectionPoolConfig::default())
197 }
198
199 pub fn memory_budget(&self) -> crate::storage::memory_budget::MemoryBudget {
203 self.inner.memory_budget
204 }
205
206 pub fn memory_accounting(&self) -> &crate::storage::memory_pools::MemoryAccounting {
210 &self.inner.memory_accounting
211 }
212
213 pub fn with_pool(
214 options: RedDBOptions,
215 pool_config: ConnectionPoolConfig,
216 ) -> RedDBResult<Self> {
217 Self::with_pool_and_limits(
218 options,
219 pool_config,
220 super::resource_limits::ResourceLimits::from_env(),
221 )
222 }
223
224 pub(super) fn with_pool_and_limits(
225 options: RedDBOptions,
226 pool_config: ConnectionPoolConfig,
227 resource_limits: super::resource_limits::ResourceLimits,
228 ) -> RedDBResult<Self> {
229 options
230 .replication
231 .validate_failover_profile()
232 .map_err(RedDBError::InvalidConfig)?;
233 let boot_open_start_ms = std::time::SystemTime::now()
241 .duration_since(std::time::UNIX_EPOCH)
242 .map(|d| d.as_millis() as u64)
243 .unwrap_or(0);
244 let memory_budget = crate::storage::memory_budget::resolve_for_boot(
248 options.storage_profile.deploy_profile,
249 options.memory_budget_bytes,
250 )
251 .map_err(|err| RedDBError::InvalidConfig(err.to_string()))?;
252 crate::storage::memory_budget::log_resolved_once(&memory_budget);
253 let memory_shares = crate::storage::memory_pools::BudgetShares::resolve(
257 memory_budget,
258 options.storage_profile.deploy_profile,
259 );
260 memory_shares.log_once();
261 let embedded_single_file = options.storage_profile.deploy_profile
262 == crate::storage::DeployProfile::Embedded
263 && options.storage_profile.packaging == crate::storage::StoragePackaging::SingleFile;
264 let db = Arc::new(RedDB::open_with_options(&options).map_err(|err| {
265 match err.downcast::<crate::storage::StoreError>() {
266 Ok(store_err) => RedDBError::from(*store_err),
267 Err(err) => RedDBError::Internal(err.to_string()),
268 }
269 })?);
270 let env_config_overrides = crate::runtime::config_overlay::collect_env_overrides();
271 let cache_l1_share = memory_shares.blob_cache_l1_bytes();
274 let cache_l1_default = u64::try_from(cache_l1_share).unwrap_or(u64::MAX);
275 let configured_l1 = usize::try_from(boot_cache_u64(
276 db.store().as_ref(),
277 &env_config_overrides,
278 "cache.blob.l1_bytes_max",
279 cache_l1_default,
280 ))
281 .map_err(|_| RedDBError::InvalidConfig("cache.blob.l1_bytes_max is too large".into()))?;
282 let configured_l2 = boot_cache_u64(
283 db.store().as_ref(),
284 &env_config_overrides,
285 "cache.blob.l2_bytes_max",
286 crate::storage::cache::DEFAULT_BLOB_L2_BYTES_MAX,
287 );
288 let configured_namespaces = usize::try_from(boot_cache_u64(
289 db.store().as_ref(),
290 &env_config_overrides,
291 "cache.blob.max_namespaces",
292 u64::try_from(crate::storage::cache::DEFAULT_BLOB_MAX_NAMESPACES).unwrap_or(u64::MAX),
293 ))
294 .map_err(|_| RedDBError::InvalidConfig("cache.blob.max_namespaces is too large".into()))?;
295 if configured_namespaces == 0 {
296 return Err(RedDBError::InvalidConfig(
297 "cache.blob.max_namespaces must be greater than zero".into(),
298 ));
299 }
300 let configured_namespace_bytes = usize::try_from(boot_cache_u64(
301 db.store().as_ref(),
302 &env_config_overrides,
303 "cache.blob.namespace_bytes_max",
304 u64::try_from(configured_l1).unwrap_or(u64::MAX),
305 ))
306 .map_err(|_| {
307 RedDBError::InvalidConfig("cache.blob.namespace_bytes_max is too large".into())
308 })?;
309 if configured_namespace_bytes == 0 {
310 return Err(RedDBError::InvalidConfig(
311 "cache.blob.namespace_bytes_max must be greater than zero".into(),
312 ));
313 }
314 let result_blob_cache_config = crate::storage::cache::BlobCacheConfig::default()
315 .with_l1_bytes_max(configured_l1.min(cache_l1_share))
318 .with_namespace_bytes_max(
319 configured_namespace_bytes
320 .min(configured_l1)
321 .min(cache_l1_share),
322 )
323 .with_l2_bytes_max(if options.read_only { 0 } else { configured_l2 })
326 .with_max_namespaces(configured_namespaces);
327 let result_blob_cache = crate::storage::cache::BlobCache::open_with_internal_l2(
328 result_blob_cache_config,
329 db.store(),
330 )
331 .map_err(|err| {
332 RedDBError::Internal(format!(
333 "open single-file result Blob Cache failed: {err:?}"
334 ))
335 })?;
336 let storage_ready_ms = std::time::SystemTime::now()
337 .duration_since(std::time::UNIX_EPOCH)
338 .map(|d| d.as_millis() as u64)
339 .unwrap_or(0);
340
341 let no_auth_boot = crate::service_cli::no_auth_active(&options);
342 let runtime = Self {
343 background_tasks: Some(Arc::new(background_tasks::RuntimeBackgroundTasks::default())),
344 inner: Arc::new(RuntimeInner {
345 functions: parking_lot::RwLock::new(
346 super::function_catalog::FunctionCatalog::load(&db)?,
347 ),
348 db: db.clone(),
349 layout: PhysicalLayout::from_options(&options),
350 embedded_single_file,
351 memory_budget,
352 memory_accounting: Arc::new(
353 crate::storage::memory_pools::MemoryAccounting::from_shares(
354 memory_budget,
355 memory_shares,
356 ),
357 ),
358 memory_reservations: parking_lot::Mutex::new(
359 super::memory_admission::MemoryReservations::default(),
360 ),
361 mvcc_maintenance: parking_lot::Mutex::new(Default::default()),
362 indices: IndexCatalog::register_default_vector_graph(
363 options.has_capability(crate::api::Capability::Table),
364 options.has_capability(crate::api::Capability::Graph),
365 ),
366 pool_config,
367 pool: Mutex::new(PoolState::default()),
368 execution_admission: parking_lot::RwLock::new(
369 crate::transport::execution_admission::ExecutionAdmission::for_host(),
370 ),
371 execution_queue_timeout_ms: AtomicU64::new(30_000),
372 started_at_unix_ms: SystemTime::now()
373 .duration_since(UNIX_EPOCH)
374 .unwrap_or_default()
375 .as_millis(),
376 probabilistic: super::probabilistic_store::ProbabilisticStore::new(),
377 index_store: super::index_store::IndexStore::new(),
378 cdc: crate::replication::cdc::CdcBuffer::new(100_000),
379 checkpoint_projection_stats: super::CheckpointProjectionStats::default(),
380 scrub_state: parking_lot::Mutex::new(super::ScrubRuntimeState::default()),
381 checkpoint_columnar_emission_budget_chunks: options
382 .checkpoint_columnar_emission_budget_chunks,
383 columnar_projection_size_floor_rows: options.columnar_projection_size_floor_rows,
384 backup_scheduler: crate::replication::scheduler::BackupScheduler::new(3600),
385 query_cache: parking_lot::RwLock::new(
386 crate::storage::query::planner::cache::PlanCache::new(1000),
387 ),
388 result_blob_cache,
389 result_cache_format_lock: parking_lot::Mutex::new(()),
390 result_blob_promotion: parking_lot::Mutex::new(()),
391 result_blob_entries: parking_lot::RwLock::new((
392 HashMap::new(),
393 std::collections::VecDeque::new(),
394 true,
395 0,
396 HashSet::new(),
397 None,
398 db.store().result_cache_epoch().unwrap_or(0),
399 )),
400 ask_answer_cache_entries: parking_lot::RwLock::new((
401 HashSet::new(),
402 std::collections::VecDeque::new(),
403 )),
404 result_cache_hits: std::sync::atomic::AtomicU64::new(0),
405 result_cache_misses: std::sync::atomic::AtomicU64::new(0),
406 result_cache_evictions: std::sync::atomic::AtomicU64::new(0),
407 ask_daily_spend: parking_lot::RwLock::new(HashMap::new()),
408 queue_message_locks: parking_lot::RwLock::new(HashMap::new()),
409 rmw_locks: RmwLockTable::new(),
410 planner_dirty_tables: parking_lot::RwLock::new(HashSet::new()),
411 ec_registry: Arc::new(crate::ec::config::EcRegistry::new()),
412 config_registry: Arc::new(crate::auth::registry::ConfigRegistry::new()),
413 ec_worker: crate::ec::worker::EcWorker::new(),
414 auth_store: parking_lot::RwLock::new(None),
415 oauth_validator: parking_lot::RwLock::new(None),
416 browser_token_authority: parking_lot::RwLock::new(None),
417 views: parking_lot::RwLock::new(HashMap::new()),
418 materialized_views: parking_lot::RwLock::new(
419 crate::storage::cache::result::MaterializedViewCache::new(),
420 ),
421 retention_sweeper: parking_lot::RwLock::new(
422 crate::runtime::retention_sweeper::RetentionSweeperState::new(),
423 ),
424 transaction_state: crate::runtime::transaction_state::TransactionState::new(),
425 env_config_overrides,
426 lock_manager: Arc::new({
427 let env = crate::runtime::config_overlay::collect_env_overrides();
432 let timeout_ms = env
433 .get("concurrency.locking.deadlock_timeout_ms")
434 .and_then(|raw| raw.parse::<u64>().ok())
435 .unwrap_or_else(|| {
436 match crate::runtime::config_matrix::default_for(
437 "concurrency.locking.deadlock_timeout_ms",
438 ) {
439 Some(crate::serde_json::Value::Number(n)) => n as u64,
440 _ => 5000,
441 }
442 });
443 let cfg = crate::runtime::lock_manager::LockConfig {
444 default_timeout: std::time::Duration::from_millis(timeout_ms),
445 ..Default::default()
446 };
447 crate::runtime::lock_manager::LockManager::new(cfg)
448 }),
449 rls_policies: parking_lot::RwLock::new(HashMap::new()),
450 rls_enabled_tables: parking_lot::RwLock::new(HashSet::new()),
451 foreign_tables: Arc::new(crate::storage::fdw::ForeignTableRegistry::with_builtins()),
452 pending_tombstones: parking_lot::RwLock::new(HashMap::new()),
453 pending_versioned_updates: parking_lot::RwLock::new(HashMap::new()),
454 pending_queue_dedup: parking_lot::RwLock::new(HashMap::new()),
455 pending_kv_watch_events: parking_lot::RwLock::new(HashMap::new()),
456 pending_vault_writes: parking_lot::RwLock::new(HashMap::new()),
457 pending_store_wal_actions: parking_lot::RwLock::new(HashMap::new()),
458 pending_logical_records: parking_lot::Mutex::new(HashMap::new()),
459 pending_commit_policies: parking_lot::Mutex::new(HashMap::new()),
460 logical_publication: parking_lot::ReentrantMutex::new(()),
461 logical_allocated_lsn: std::sync::atomic::AtomicU64::new(0),
462 pending_claim_locks: parking_lot::RwLock::new(HashMap::new()),
463 queue_wait_registry: std::sync::Arc::new(
464 crate::runtime::queue_wait_registry::QueueWaitRegistry::new(),
465 ),
466 pending_queue_wakes: parking_lot::RwLock::new(HashMap::new()),
467 tenant_tables: parking_lot::RwLock::new(HashMap::new()),
468 ddl_epoch: std::sync::atomic::AtomicU64::new(0),
469 write_gate: Arc::new(crate::runtime::write_gate::WriteGate::from_options(
470 &options,
471 )),
472 lifecycle: crate::runtime::lifecycle::Lifecycle::new(),
473 resource_limits,
474 query_budget_slots: AtomicU64::new(0),
475 audit_log: {
476 let data_path = if embedded_single_file || options.read_only {
492 std::env::temp_dir()
493 .join("reddb-embedded-runtime")
494 .join(format!("audit-{}", std::process::id()))
495 } else {
496 options
497 .data_path
498 .clone()
499 .unwrap_or_else(|| std::env::temp_dir().join("reddb"))
500 };
501 let (audit_dest, _) = crate::api::tier_wiring::current_log_destinations();
502 let audit_dest = if options.read_only {
503 crate::storage::layout::LogDestination::Stderr
504 } else {
505 audit_dest
506 };
507 if !matches!(audit_dest, crate::storage::layout::LogDestination::File(_))
508 && (embedded_single_file
509 || options.read_only
510 || options
511 .metadata
512 .contains_key(crate::api::EPHEMERAL_RUNTIME_METADATA_KEY))
513 {
514 let audit_path = reddb_file::layout::sibling_path(
524 &data_path,
525 &reddb_file::layout::sidecar_file_name(&data_path, "audit.log"),
526 );
527 Arc::new(crate::runtime::audit_log::AuditLogger::with_path(
528 audit_path,
529 ))
530 } else {
531 Arc::new(crate::runtime::audit_log::AuditLogger::for_destination(
532 &audit_dest,
533 &data_path,
534 ))
535 }
536 },
537 control_event_ledger: parking_lot::RwLock::new(Arc::new(
538 crate::runtime::control_events::RuntimeLedger::new(db.store()),
539 )),
540 control_event_config: options.control_events,
541 query_audit: Arc::new(crate::runtime::query_audit::QueryAuditStream::new(
542 db.store(),
543 options.query_audit.clone(),
544 )),
545 lease_lifecycle: std::sync::OnceLock::new(),
546 routing_primary_endpoints: parking_lot::RwLock::new(Default::default()),
547 replica_apply_metrics: std::sync::Arc::new(
548 crate::replication::logical::ReplicaApplyMetrics::default(),
549 ),
550 replica_link_metrics: std::sync::Arc::new(
551 crate::replication::reconnect::ReplicaLinkMetrics::default(),
552 ),
553 quota_bucket: crate::runtime::quota_bucket::QuotaBucket::from_env(),
554 schema_vocabulary: parking_lot::RwLock::new(
555 crate::runtime::schema_vocabulary::SchemaVocabulary::new(),
556 ),
557 description_enrichment: parking_lot::Mutex::new(Default::default()),
558 slow_query_logger: {
559 let fallback_dir = if embedded_single_file || options.read_only {
576 std::env::temp_dir()
577 .join("reddb-embedded-runtime")
578 .join(format!("slow-{}", std::process::id()))
579 } else {
580 options
581 .data_path
582 .as_ref()
583 .and_then(|p| p.parent().map(std::path::PathBuf::from))
584 .unwrap_or_else(|| std::env::temp_dir().join("reddb"))
585 };
586 let threshold_ms = std::env::var("RED_SLOW_QUERY_THRESHOLD_MS")
587 .ok()
588 .and_then(|s| s.parse::<u64>().ok())
589 .unwrap_or(1000);
590 let sample_pct = std::env::var("RED_SLOW_QUERY_SAMPLE_PCT")
591 .ok()
592 .and_then(|s| s.parse::<u8>().ok())
593 .unwrap_or(100);
594 let (_, slow_dest) = crate::api::tier_wiring::current_log_destinations();
595 let slow_dest = if options.read_only {
596 crate::storage::layout::LogDestination::Stderr
597 } else {
598 slow_dest
599 };
600 crate::telemetry::slow_query_logger::SlowQueryLogger::for_destination(
601 &slow_dest,
602 &fallback_dir,
603 threshold_ms,
604 sample_pct,
605 )
606 },
607 slow_query_store: crate::telemetry::slow_query_store::SlowQueryStore::new(
608 crate::telemetry::slow_query_store::DEFAULT_CAP,
609 ),
610 kv_stats: crate::runtime::KvStatsCounters::default(),
611 metrics_ingest_stats: crate::runtime::MetricsIngestCounters::default(),
612 metrics_tenant_activity_stats:
613 crate::runtime::MetricsTenantActivityCounters::default(),
614 claim_telemetry: Arc::new(
615 crate::runtime::claim_telemetry::ClaimTelemetryCounters::default(),
616 ),
617 queue_telemetry: Arc::new(
618 crate::runtime::queue_telemetry::QueueTelemetryCounters::default(),
619 ),
620 query_latency_telemetry: Arc::new(
621 crate::runtime::query_latency_telemetry::QueryLatencyTelemetry::default(),
622 ),
623 occupancy_sampler: Arc::new(
624 crate::runtime::occupancy_sampler::OccupancySampler::new(),
625 ),
626 node_load_telemetry: Arc::new(
627 crate::runtime::node_load_telemetry::NodeLoadTelemetry::default(),
628 ),
629 queue_presence: Arc::new(
630 crate::storage::queue::presence::ConsumerPresenceRegistry::new(),
631 ),
632 vector_introspection: Arc::new(
633 crate::storage::vector::introspection::VectorIntrospectionRegistry::new(),
634 ),
635 kv_tag_index: crate::runtime::KvTagIndex::default(),
636 chain_tip_cache: parking_lot::Mutex::new(HashMap::new()),
637 chain_integrity_broken: parking_lot::Mutex::new(HashMap::new()),
638 integrity_tombstones: parking_lot::Mutex::new(Vec::new()),
639 integrity_tombstones_state: std::sync::atomic::AtomicU8::new(0),
640 }),
641 };
642
643 runtime.install_hash_candidate_admission();
644
645 crate::telemetry::operator_event::install_global_audit_sink(Arc::clone(
651 &runtime.inner.audit_log,
652 ));
653
654 runtime
657 .inner
658 .slow_query_logger
659 .attach_store(Arc::clone(&runtime.inner.slow_query_store));
660
661 runtime
669 .inner
670 .lifecycle
671 .set_restore_started_at_ms(boot_open_start_ms);
672 runtime
673 .inner
674 .lifecycle
675 .set_restore_ready_at_ms(storage_ready_ms);
676 runtime
677 .inner
678 .lifecycle
679 .set_wal_replay_started_at_ms(boot_open_start_ms);
680 runtime
681 .inner
682 .lifecycle
683 .set_wal_replay_ready_at_ms(storage_ready_ms);
684
685 let inherited_replication_lsn = if runtime.inner.db.replication.is_some() {
686 let promotion_lsn = runtime
687 .primary_replica_timeline_history()?
688 .and_then(|history| history.entries.last().map(|entry| entry.fork_lsn))
689 .unwrap_or(0);
690 runtime
691 .config_u64("replication.last_applied_lsn", 0)
692 .max(promotion_lsn)
693 } else {
694 0
695 };
696 let restored_cdc_lsn = runtime
697 .inner
698 .db
699 .replication
700 .as_ref()
701 .map(|repl| {
702 repl.logical_wal_spool
703 .as_ref()
704 .map(|spool| spool.current_lsn())
705 .unwrap_or(0)
706 })
707 .unwrap_or(0)
708 .max(runtime.config_u64("timeline.last_archived_lsn", 0))
709 .max(inherited_replication_lsn);
710 runtime.inner.cdc.set_current_lsn(restored_cdc_lsn);
711 runtime
712 .inner
713 .logical_allocated_lsn
714 .store(restored_cdc_lsn, std::sync::atomic::Ordering::Release);
715 runtime.rehydrate_snapshot_xid_floor()?;
716 runtime.rehydrate_vcs_snapshot_pins()?;
717 assert!(runtime
718 .inner
719 .db
720 .runtime
721 .set(super::WeakRuntime(Arc::downgrade(&runtime.inner)))
722 .is_ok());
723 assert!(
724 runtime
725 .inner
726 .db
727 .store()
728 .snapshot_manager
729 .set(runtime.snapshot_manager())
730 .is_ok(),
731 "a new runtime owns the store transaction outcomes"
732 );
733 runtime
734 .bootstrap_system_keyed_collections()
735 .map_err(|err| RedDBError::Internal(format!("bootstrap system collections: {err}")))?;
736 runtime.rehydrate_declared_column_schemas();
737 super::control_event_index::prepare(&runtime)?;
740 super::queue_storage_index::prepare(&runtime)?;
741 runtime.rehydrate_runtime_index_registry()?;
742 if runtime
743 .db()
744 .store()
745 .config()
746 .resident_memory_bytes_max
747 .is_some()
748 {
749 runtime
750 .db()
751 .store()
752 .checkpoint_missing_secondary_indices()?;
753 }
754 runtime.rehydrate_kv_tags()?;
755 runtime
756 .load_probabilistic_state()
757 .map_err(|err| RedDBError::Internal(format!("load probabilistic state: {err}")))?;
758
759 runtime.rehydrate_tenant_tables();
763 runtime.rehydrate_materialized_view_descriptors()?;
768 runtime.recover_store_forks()?;
769 if let Some(repl) = &runtime.inner.db.replication {
770 repl.wal_buffer.set_current_lsn(restored_cdc_lsn);
771 if let Some(spool) = &repl.logical_wal_spool {
772 spool.restore_lsn_floor(restored_cdc_lsn);
773 }
774 }
775 runtime.recover_transactional_replication()?;
776
777 {
779 let sys = SystemInfo::collect();
780 let store = runtime.inner.db.store();
781 store.with_boot_system_info_update(|| {
782 store.set_config_tree(
783 "system",
784 &crate::serde_json::json!({
785 "pid": sys.pid,
786 "cpu_cores": sys.cpu_cores,
787 "total_memory_bytes": sys.total_memory_bytes,
788 "available_memory_bytes": sys.available_memory_bytes,
789 "os": sys.os,
790 "arch": sys.arch,
791 "hostname": sys.hostname,
792 "started_at": SystemTime::now()
793 .duration_since(UNIX_EPOCH)
794 .unwrap_or_default()
795 .as_millis() as u64
796 }),
797 )
798 });
799
800 let store = runtime.inner.db.store();
802 if store
803 .get_collection("red.config")
804 .map(|m| m.query_all(|_| true).len())
805 .unwrap_or(0)
806 <= 10
807 {
808 store.set_config_tree("ai", &crate::json!({
809 "default": crate::json!({
810 "provider": "openai",
811 "model": crate::ai::DEFAULT_OPENAI_PROMPT_MODEL
812 }),
813 "max_embedding_inputs": 256,
814 "max_prompt_batch": 256,
815 "timeout": crate::json!({ "connect_secs": 10, "read_secs": 90, "write_secs": 30 })
816 }));
817 store.set_config_tree(
818 "server",
819 &crate::json!({
820 "max_scan_limit": 1000,
821 "max_body_size": 1048576,
822 "read_timeout_ms": 5000,
823 "write_timeout_ms": 5000
824 }),
825 );
826 store.set_config_tree(
827 "storage",
828 &crate::json!({
829 "page_size": 4096,
830 "page_cache_capacity": 100000,
831 "auto_checkpoint_pages": 1000,
832 "snapshot_retention": 16,
833 "verify_checksums": true,
834 "segment": crate::json!({
835 "max_entities": 100000,
836 "max_bytes": 268435456_u64,
837 "compression_level": 6
838 }),
839 "hnsw": crate::json!({ "m": 16, "ef_construction": 100, "ef_search": 50 }),
840 "ivf": crate::json!({ "n_lists": 100, "n_probes": 10 }),
841 "bm25": crate::json!({ "k1": 1.2, "b": 0.75 })
842 }),
843 );
844 store.set_config_tree(
845 "search",
846 &crate::json!({
847 "rag": crate::json!({
848 "max_chunks_per_source": 10,
849 "max_total_chunks": 25,
850 "similarity_threshold": 0.8,
851 "graph_depth": 2,
852 "min_relevance": 0.3
853 }),
854 "fusion": crate::json!({
855 "vector_weight": 0.5,
856 "graph_weight": 0.3,
857 "table_weight": 0.2,
858 "dedup_threshold": 0.85
859 })
860 }),
861 );
862 store.set_config_tree(
863 "auth",
864 &crate::json!({
865 "enabled": false,
866 "session_ttl_secs": 3600,
867 "require_auth": false
868 }),
869 );
870 store.set_config_tree(
871 "query",
872 &crate::json!({
873 "connection_pool": crate::json!({ "max_connections": 64, "max_idle": 16 }),
874 "max_recursion_depth": 1000
875 }),
876 );
877 store.set_config_tree(
878 "indexes",
879 &crate::json!({
880 "auto_select": true,
881 "bloom_filter": crate::json!({
882 "enabled": true,
883 "false_positive_rate": 0.01,
884 "prune_on_scan": true
885 }),
886 "hash": crate::json!({ "enabled": true }),
887 "bitmap": crate::json!({ "enabled": true, "max_cardinality": 1000 }),
888 "spatial": crate::json!({ "enabled": true })
889 }),
890 );
891 store.set_config_tree(
892 "probabilistic",
893 &crate::json!({
894 "hll_registers": 16384,
895 "sketch_default_width": 1000,
896 "sketch_default_depth": 5,
897 "filter_default_capacity": 100000
898 }),
899 );
900 store.set_config_tree(
901 "timeseries",
902 &crate::json!({
903 "default_chunk_size": 1024,
904 "compression": crate::json!({
905 "timestamps": "delta_of_delta",
906 "values": "gorilla_xor"
907 }),
908 "default_retention_days": 0
909 }),
910 );
911 store.set_config_tree(
912 "queue",
913 &crate::json!({
914 "default_max_size": 0,
915 "default_max_attempts": 3,
916 "visibility_timeout_ms": 30000,
917 "consumer_idle_timeout_ms": 60000
918 }),
919 );
920 store.set_config_tree(
921 "backup",
922 &crate::json!({
923 "enabled": false,
924 "interval_secs": 3600,
925 "retention_count": 24,
926 "upload": false,
927 "backend": "local"
928 }),
929 );
930 store.set_config_tree(
931 "wal",
932 &crate::json!({
933 "archive": crate::json!({
934 "enabled": false,
935 "retention_hours": 168,
936 "prefix": reddb_file::backup_wal_prefix("")
937 })
938 }),
939 );
940 store.set_config_tree(
941 "cdc",
942 &crate::json!({
943 "enabled": true,
944 "buffer_size": 100000
945 }),
946 );
947 store.set_config_tree(
948 "secret",
949 &crate::json!({
950 "auto_encrypt": true,
951 "auto_decrypt": true
952 }),
953 );
954 }
955
956 crate::runtime::config_matrix::heal_critical_keys(store.as_ref());
963 seed_storage_deploy_config(store.as_ref(), options.storage_profile);
964
965 let lehman_yao = runtime.config_bool("storage.btree.lehman_yao", true);
972 crate::storage::engine::btree::lehman_yao::set_enabled(lehman_yao);
973 if lehman_yao {
974 tracing::info!(
975 "storage.btree.lehman_yao=true — lock-free concurrent descent enabled"
976 );
977 }
978
979 let overlay_path = crate::runtime::config_overlay::config_file_path();
984 let _ =
985 crate::runtime::config_overlay::apply_config_file(store.as_ref(), &overlay_path);
986 }
987
988 {
992 let store = runtime.inner.db.store();
993 for name in crate::application::vcs_collections::ALL {
994 let _ = store.get_or_create_collection(*name);
995 }
996 store.set_config_tree(
999 crate::application::vcs_collections::CONFIG_NAMESPACE,
1000 &crate::json!({
1001 "default_branch": "main",
1002 "author": crate::json!({
1003 "name": "reddb",
1004 "email": "reddb@localhost"
1005 }),
1006 "protected_branches": crate::json!(["main"]),
1007 "closure": crate::json!({
1008 "enabled": true,
1009 "lazy": true
1010 }),
1011 "merge": crate::json!({
1012 "default_strategy": "auto",
1013 "fast_forward": true
1014 })
1015 }),
1016 );
1017 }
1018
1019 {
1022 let store = runtime.inner.db.store();
1023 for name in crate::application::migration_collections::ALL {
1024 let _ = store.get_or_create_collection(*name);
1025 }
1026 }
1027
1028 let _ = crate::application::topology_collections::ensure(&runtime);
1032
1033 runtime.prepare_paged_counts()?;
1038
1039 if no_auth_boot {
1043 use crate::runtime::control_events::{
1044 ActorRef, ControlEvent, ControlEventCtx, EventKind, Outcome,
1045 };
1046 let ledger = std::sync::Arc::clone(&runtime.inner.control_event_ledger.read());
1047 let ctx = ControlEventCtx {
1048 actor: ActorRef::System("boot"),
1049 scope: None,
1050 request_id: None,
1051 trace_id: None,
1052 };
1053 let event = ControlEvent {
1054 kind: EventKind::AuthDisabled,
1055 outcome: Outcome::Allowed,
1056 action: std::borrow::Cow::Borrowed("auth.disabled"),
1057 resource: Some("server".to_string()),
1058 reason: Some("--no-auth / --dev".to_string()),
1059 matched_policy_id: None,
1060 fields: std::collections::HashMap::new(),
1061 };
1062 if let Err(err) = ledger.emit(&ctx, event) {
1063 tracing::warn!(
1064 target: "reddb::security",
1065 error = %err,
1066 "could not record the auth-disabled boot in the control-event ledger"
1067 );
1068 }
1069 }
1070
1071 {
1085 let weak = Arc::downgrade(&runtime.inner);
1086 runtime
1087 .background_tasks
1088 .as_ref()
1089 .expect("foreground runtime")
1090 .spawn(
1091 std::thread::Builder::new().name("reddb-maintenance".into()),
1092 move |stopped| {
1093 let tick = std::time::Duration::from_millis(200);
1094 let work_interval = std::time::Duration::from_secs(60);
1095 let mut last_work = std::time::Instant::now();
1096 let mut allocator_pressure =
1097 super::allocator_pressure::AllocatorPressure::default();
1098 loop {
1099 std::thread::park_timeout(tick);
1100 if stopped.load(AtomicOrdering::Acquire) {
1101 break;
1102 }
1103 let Some(inner) = weak.upgrade() else {
1104 break;
1107 };
1108 if last_work.elapsed() >= work_interval {
1109 let _stats = inner.db.store().context_index().stats();
1110 last_work = std::time::Instant::now();
1111 }
1112 let rt = RedDBRuntime {
1113 background_tasks: None,
1114 inner,
1115 };
1116 rt.try_reclaim_history_tick();
1117 allocator_pressure.tick();
1118 }
1119 },
1120 );
1121 }
1122
1123 {
1129 let weak = Arc::downgrade(&runtime.inner);
1130 runtime
1131 .background_tasks
1132 .as_ref()
1133 .expect("foreground runtime")
1134 .spawn(
1135 std::thread::Builder::new().name("reddb-cache-sweeper".into()),
1136 move |stopped| loop {
1137 std::thread::park_timeout(std::time::Duration::from_millis(250));
1138 if stopped.load(AtomicOrdering::Acquire) {
1139 break;
1140 }
1141 let Some(inner) = weak.upgrade() else {
1142 break;
1143 };
1144 let _ = crate::storage::cache::sweeper::BlobCacheSweeper::sweep_expired(
1145 &inner.result_blob_cache,
1146 crate::storage::cache::sweeper::SweepLimit::Either {
1147 entries: 512,
1148 millis: 5,
1149 },
1150 );
1151 let rt = RedDBRuntime {
1152 background_tasks: None,
1153 inner,
1154 };
1155 rt.promote_volatile_result_cache_entry();
1156 },
1157 );
1158 }
1159
1160 {
1162 let store = runtime.inner.db.store();
1163 let mut backup_enabled = false;
1164 let mut backup_interval = 3600u64;
1165
1166 if let Some(manager) = store.get_collection("red.config") {
1167 manager.for_each_entity(|entity| {
1168 if let Some(row) = entity.data.as_row() {
1169 let key = row.get_field("key").and_then(|v| match v {
1170 reddb_types::Value::Text(s) => Some(s.as_ref()),
1171 _ => None,
1172 });
1173 let val = row.get_field("value");
1174 if key == Some("backup.enabled") {
1175 backup_enabled = match val {
1176 Some(reddb_types::Value::Boolean(true)) => true,
1177 Some(reddb_types::Value::Text(s)) => &**s == "true",
1178 _ => false,
1179 };
1180 } else if key == Some("backup.interval_secs") {
1181 if let Some(reddb_types::Value::Integer(n)) = val {
1182 backup_interval = *n as u64;
1183 }
1184 }
1185 }
1186 true
1187 });
1188 }
1189
1190 if backup_enabled {
1191 runtime.inner.backup_scheduler.set_interval(backup_interval);
1192 let rt = runtime.clone();
1193 runtime
1194 .inner
1195 .backup_scheduler
1196 .start(move || rt.trigger_backup().map_err(|e| format!("{}", e)));
1197 }
1198 }
1199
1200 {
1202 runtime
1203 .inner
1204 .ec_registry
1205 .load_from_config_store(runtime.inner.db.store().as_ref());
1206 if !runtime.inner.db.options().read_only
1207 && !runtime.inner.ec_registry.async_configs().is_empty()
1208 {
1209 runtime.inner.ec_worker.start(
1210 Arc::clone(&runtime.inner.ec_registry),
1211 Arc::clone(&runtime.inner.db.store()),
1212 );
1213 }
1214 }
1215
1216 if let (true, crate::replication::ReplicationRole::Replica { primary_addr }) = (
1217 runtime.inner.db.options().replica_loop_enabled
1218 && !runtime.inner.db.options().read_only,
1219 runtime.inner.db.options().replication.role.clone(),
1220 ) {
1221 let rt = runtime.clone();
1222 std::thread::Builder::new()
1223 .name("reddb-replica".into())
1224 .spawn(move || rt.run_replica_loop(primary_addr))
1225 .ok();
1226 }
1227
1228 runtime.inner.lifecycle.mark_ready();
1233
1234 #[cfg(not(test))]
1235 if runtime
1236 .db()
1237 .store()
1238 .config()
1239 .resident_memory_bytes_max
1240 .is_some()
1241 && !runtime.db().options().read_only
1242 {
1243 runtime.start_count_projection_worker();
1244 }
1245
1246 {
1255 let weak_inner = Arc::downgrade(&runtime.inner);
1256 runtime
1257 .background_tasks
1258 .as_ref()
1259 .expect("foreground runtime")
1260 .spawn(
1261 std::thread::Builder::new()
1262 .name("reddb-mv-scheduler".into())
1263 .stack_size(8 * 1024 * 1024),
1268 move |stopped| loop {
1269 std::thread::park_timeout(std::time::Duration::from_millis(50));
1270 if stopped.load(AtomicOrdering::Acquire) {
1271 break;
1272 }
1273 let Some(inner) = weak_inner.upgrade() else {
1274 break;
1275 };
1276 let rt = RedDBRuntime {
1277 background_tasks: None,
1278 inner,
1279 };
1280 rt.refresh_due_materialized_views();
1281 },
1282 );
1283 }
1284
1285 if !runtime.write_gate().is_read_only() {
1295 let weak_inner = Arc::downgrade(&runtime.inner);
1296 runtime
1297 .background_tasks
1298 .as_ref()
1299 .expect("foreground runtime")
1300 .spawn(
1301 std::thread::Builder::new().name("reddb-retention-sweeper".into()),
1302 move |stopped| loop {
1303 std::thread::park_timeout(std::time::Duration::from_millis(500));
1304 if stopped.load(AtomicOrdering::Acquire) {
1305 break;
1306 }
1307 let Some(inner) = weak_inner.upgrade() else {
1308 break;
1309 };
1310 let rt = RedDBRuntime {
1311 background_tasks: None,
1312 inner,
1313 };
1314 rt.sweep_retention_tick(
1315 crate::runtime::retention_sweeper::DEFAULT_SWEEPER_BATCH,
1316 );
1317 rt.sweep_individual_ttl_tick(
1318 crate::runtime::retention_sweeper::DEFAULT_SWEEPER_BATCH,
1319 );
1320 },
1321 );
1322 }
1323
1324 runtime
1330 .inner
1331 .db
1332 .store()
1333 .register_entity_id(crate::storage::EntityId::new(
1334 crate::storage::FIRST_USER_ENTITY_ID - 1,
1335 ));
1336
1337 Ok(runtime)
1338 }
1339
1340 fn rehydrate_snapshot_xid_floor(&self) -> RedDBResult<()> {
1341 let store = self.inner.db.store();
1342 let snapshot_manager = self.inner.transaction_state.snapshot_manager();
1343 let base_high_water_mark = store.startup_xid_high_water_mark();
1344 if let Some(high_water_mark) = base_high_water_mark {
1345 snapshot_manager.observe_committed_xid(high_water_mark);
1346 }
1347 for collection in store.list_collections() {
1348 let Some(manager) = store.get_collection(&collection) else {
1349 continue;
1350 };
1351 let observe = |entity: &crate::storage::UnifiedEntity| {
1352 snapshot_manager.observe_committed_xid(entity.xmin);
1353 snapshot_manager.observe_committed_xid(entity.xmax);
1354 true
1355 };
1356 if base_high_water_mark.is_some() {
1357 manager.for_each_entity(observe);
1360 } else {
1361 manager.try_for_each_entity(usize::MAX, observe)?;
1364 }
1365 }
1366 if base_high_water_mark.is_none() {
1367 store.remember_startup_xid_high_water_mark(
1368 snapshot_manager.peek_next_xid().saturating_sub(1),
1369 );
1370 }
1371 Ok(())
1372 }
1373
1374 pub(crate) fn ensure_materialized_view_backing(&self, name: &str) -> RedDBResult<()> {
1387 let store = self.inner.db.store();
1388 let mut changed = false;
1389 if store.get_collection(name).is_none() {
1390 store.get_or_create_collection(name);
1391 changed = true;
1392 }
1393 if self.inner.db.collection_contract(name).is_none() {
1394 self.inner
1395 .db
1396 .save_collection_contract(system_keyed_collection_contract(
1397 name,
1398 crate::catalog::CollectionModel::Table,
1399 ))
1400 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1401 changed = true;
1402 }
1403 if changed {
1404 self.inner
1405 .db
1406 .persist_metadata()
1407 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1408 }
1409 Ok(())
1410 }
1411
1412 pub(crate) fn drop_materialized_view_backing(&self, name: &str) -> RedDBResult<()> {
1417 let store = self.inner.db.store();
1418 if store.get_collection(name).is_none() {
1419 return Ok(());
1420 }
1421 store
1422 .drop_collection(name)
1423 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1424 if self.inner.db.collection_contract(name).is_some() {
1427 self.inner
1428 .db
1429 .remove_collection_contract(name)
1430 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1431 }
1432 self.invalidate_result_cache();
1433 self.inner
1434 .db
1435 .persist_metadata()
1436 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1437 Ok(())
1438 }
1439
1440 fn bootstrap_system_keyed_collections(&self) -> RedDBResult<()> {
1441 let mut changed = false;
1442 for (name, model) in [
1443 ("red.config", crate::catalog::CollectionModel::Config),
1444 ("red.vault", crate::catalog::CollectionModel::Vault),
1445 (
1449 crate::runtime::continuous_materialized_view::CATALOG_COLLECTION,
1450 crate::catalog::CollectionModel::Config,
1451 ),
1452 ] {
1453 if self.inner.db.store().get_collection(name).is_none() {
1454 self.inner.db.store().get_or_create_collection(name);
1455 changed = true;
1456 }
1457 if self.inner.db.collection_contract(name).is_none() {
1458 let contract = system_keyed_collection_contract(name, model);
1459 if self.inner.db.options().read_only {
1460 self.inner.db.cache_read_only_bootstrap_contract(contract);
1461 } else {
1462 self.inner
1463 .db
1464 .save_collection_contract(contract)
1465 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1466 }
1467 changed = true;
1468 }
1469 }
1470 if changed {
1471 self.inner
1472 .db
1473 .persist_metadata()
1474 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1475 }
1476 Ok(())
1477 }
1478
1479 pub fn db(&self) -> Arc<RedDB> {
1480 Arc::clone(&self.inner.db)
1481 }
1482
1483 pub fn index_store_ref(&self) -> &super::index_store::IndexStore {
1488 &self.inner.index_store
1489 }
1490
1491 pub(crate) fn schema_vocabulary_apply(
1496 &self,
1497 event: crate::runtime::schema_vocabulary::DdlEvent,
1498 ) {
1499 self.inner.schema_vocabulary.write().on_ddl(event.clone());
1500 self.schedule_collection_description_enrichment(&event);
1501 }
1502
1503 pub(crate) fn schema_vocabulary_sync_descriptions(&self, collection: &str) {
1504 let Some(contract) = self.inner.db.collection_contract(collection) else {
1505 return;
1506 };
1507 let columns = contract
1508 .declared_columns
1509 .iter()
1510 .filter_map(|column| {
1511 column
1512 .description
1513 .as_ref()
1514 .map(|description| (column.name.clone(), description.clone()))
1515 })
1516 .collect::<Vec<_>>();
1517 self.inner.schema_vocabulary.write().update_descriptions(
1518 collection,
1519 contract.description.as_deref(),
1520 &columns,
1521 );
1522 }
1523
1524 pub fn schema_vocabulary_lookup(
1529 &self,
1530 token: &str,
1531 ) -> Vec<crate::runtime::schema_vocabulary::VocabHit> {
1532 self.inner.schema_vocabulary.read().lookup(token).to_vec()
1533 }
1534
1535 pub fn set_auth_store(&self, store: Arc<crate::auth::store::AuthStore>) {
1539 *self.inner.auth_store.write() = Some(store);
1540 }
1541
1542 pub fn auth_store(&self) -> Option<Arc<crate::auth::store::AuthStore>> {
1545 self.inner.auth_store.read().clone()
1546 }
1547
1548 pub fn set_oauth_validator(&self, validator: Option<Arc<crate::auth::oauth::OAuthValidator>>) {
1552 *self.inner.oauth_validator.write() = validator;
1553 }
1554
1555 pub fn oauth_validator(&self) -> Option<Arc<crate::auth::oauth::OAuthValidator>> {
1559 self.inner.oauth_validator.read().clone()
1560 }
1561
1562 pub fn set_browser_token_authority(
1568 &self,
1569 authority: Option<Arc<crate::auth::browser_token::BrowserTokenAuthority>>,
1570 ) {
1571 *self.inner.browser_token_authority.write() = authority;
1572 }
1573
1574 pub fn browser_token_authority(
1578 &self,
1579 ) -> Option<Arc<crate::auth::browser_token::BrowserTokenAuthority>> {
1580 self.inner.browser_token_authority.read().clone()
1581 }
1582}
1583
1584#[cfg(test)]
1585mod cache_boot_tests {
1586 use super::*;
1587
1588 #[test]
1589 fn persisted_cache_limits_apply_on_next_boot_within_memory_budget() {
1590 let directory = tempfile::tempdir().expect("cache config fixture");
1591 let options = RedDBOptions::persistent(directory.path().join("cache-config.rdb"))
1592 .with_memory_budget(64 * 1024 * 1024);
1593 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1594 runtime.db().store().set_config_tree(
1595 "cache.blob",
1596 &crate::json!({
1597 "l1_bytes_max": 1024 * 1024,
1598 "l2_bytes_max": 2 * 1024 * 1024,
1599 "namespace_bytes_max": 256 * 1024,
1600 "max_namespaces": 7
1601 }),
1602 );
1603 runtime.flush().expect("persist cache config");
1604 drop(runtime);
1605
1606 let reopened = RedDBRuntime::with_options(options).expect("configured runtime");
1607 let stats = reopened.result_blob_cache().stats();
1608 assert_eq!(stats.l1_bytes_max(), 1024 * 1024);
1609 assert_eq!(stats.l2_bytes_max(), 2 * 1024 * 1024);
1610 assert_eq!(stats.namespace_bytes_max(), 256 * 1024);
1611 assert_eq!(stats.max_namespaces(), 7);
1612 }
1613
1614 #[test]
1615 fn single_file_l2_survives_runtime_restart_without_sidecar() {
1616 let directory = tempfile::tempdir().expect("single-file L2 fixture");
1617 let path = directory.path().join("cache-l2.rdb");
1618 let options = RedDBOptions::persistent(&path);
1619 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1620 runtime
1621 .result_blob_cache()
1622 .put(
1623 "tenant",
1624 "key",
1625 crate::storage::cache::BlobCachePut::new(b"durable".to_vec()).with_policy(
1626 crate::storage::cache::BlobCachePolicy::default()
1627 .l1_admission(crate::storage::cache::L1Admission::Never),
1628 ),
1629 )
1630 .expect("write internal L2");
1631 assert!(runtime.result_blob_cache().l2_path().is_none());
1632 runtime.flush().expect("checkpoint internal L2");
1633 drop(runtime);
1634
1635 let reopened = RedDBRuntime::with_options(options).expect("reopen runtime");
1636 assert_eq!(
1637 reopened
1638 .result_blob_cache()
1639 .get("tenant", "key")
1640 .expect("L2 hit after restart")
1641 .bytes()
1642 .as_ref(),
1643 b"durable"
1644 );
1645 drop(reopened);
1646 let siblings: Vec<_> = std::fs::read_dir(directory.path())
1647 .expect("list fixture")
1648 .filter_map(Result::ok)
1649 .map(|entry| entry.file_name().to_string_lossy().into_owned())
1650 .collect();
1651 assert_eq!(siblings, vec!["cache-l2.rdb"]);
1652 }
1653
1654 #[test]
1655 fn single_file_l2_deadline_survives_restart_and_sweeps_atomically() {
1656 let directory = tempfile::tempdir().expect("single-file L2 TTL fixture");
1657 let path = directory.path().join("cache-l2-ttl.rdb");
1658 let options = RedDBOptions::persistent(&path);
1659 let mut runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1660 drop(runtime.background_tasks.take());
1661 runtime
1662 .result_blob_cache()
1663 .put(
1664 "tenant",
1665 "expired",
1666 crate::storage::cache::BlobCachePut::new(b"expired".to_vec()).with_policy(
1667 crate::storage::cache::BlobCachePolicy::default()
1668 .expires_at_unix_ms(1)
1669 .l1_admission(crate::storage::cache::L1Admission::Never),
1670 ),
1671 )
1672 .expect("write expiring L2 entry");
1673 runtime.flush().expect("checkpoint L2 deadline");
1674 drop(runtime);
1675
1676 let mut reopened = RedDBRuntime::with_options(options).expect("reopen runtime");
1677 drop(reopened.background_tasks.take());
1678 let manager = reopened
1679 .db()
1680 .store()
1681 .get_collection(crate::storage::cache::blob::INTERNAL_L2_COLLECTION)
1682 .expect("L2 collection");
1683 assert_eq!(manager.scheduled_metadata_expiry_count(), 1);
1684 let bytes_before = reopened.result_blob_cache().stats().l2_bytes_in_use();
1685 assert!(bytes_before > 0);
1686
1687 assert_eq!(reopened.sweep_individual_ttl_tick_at(64, u64::MAX), 0);
1690 assert_eq!(
1691 reopened.result_blob_cache().stats().l2_bytes_in_use(),
1692 bytes_before
1693 );
1694 let report = crate::storage::cache::BlobCacheSweeper::sweep_expired(
1695 reopened.result_blob_cache(),
1696 crate::storage::cache::SweepLimit::Entries(1),
1697 );
1698 assert_eq!(report.entries_evicted, 1);
1699 assert_eq!(reopened.result_blob_cache().stats().l2_bytes_in_use(), 0);
1700 assert_eq!(manager.scheduled_metadata_expiry_count(), 0);
1701 }
1702
1703 #[test]
1704 fn expired_l2_payload_pages_are_reused() {
1705 let directory = tempfile::tempdir().expect("L2 page reuse fixture");
1706 let path = directory.path().join("cache-l2-reuse.rdb");
1707 let options = RedDBOptions::persistent(&path)
1708 .with_memory_budget(32 * 1024 * 1024)
1709 .with_bounded_disk_residency(true);
1710 let mut runtime = RedDBRuntime::with_options(options.clone()).expect("runtime");
1711 drop(runtime.background_tasks.take());
1712 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1713 let payload: Vec<u8> = std::iter::repeat_with(|| {
1714 state ^= state << 13;
1715 state ^= state >> 7;
1716 state ^= state << 17;
1717 state.to_le_bytes()[0]
1718 })
1719 .take(64 * 1024)
1720 .collect();
1721 for id in 0..8 {
1722 runtime
1723 .result_blob_cache()
1724 .put(
1725 "reuse",
1726 format!("expired-{id}"),
1727 crate::storage::cache::BlobCachePut::new(payload.clone()).with_policy(
1728 crate::storage::cache::BlobCachePolicy::default()
1729 .expires_at_unix_ms(1)
1730 .l1_admission(crate::storage::cache::L1Admission::Never),
1731 ),
1732 )
1733 .expect("expired L2 payload");
1734 }
1735 runtime.checkpoint().expect("initial L2 checkpoint");
1736 let initial_pages = runtime
1737 .db()
1738 .store()
1739 .pager()
1740 .expect("pager")
1741 .page_count()
1742 .expect("initial page count");
1743 drop(runtime);
1744
1745 let mut runtime = RedDBRuntime::with_options(options).expect("reopened runtime");
1746 drop(runtime.background_tasks.take());
1747 let store = runtime.db().store();
1748 let pager = store.pager().expect("pager");
1749 let report = crate::storage::cache::BlobCacheSweeper::sweep_expired(
1750 runtime.result_blob_cache(),
1751 crate::storage::cache::SweepLimit::Entries(64),
1752 );
1753 assert_eq!(report.entries_evicted, 8);
1754 runtime.checkpoint().expect("expired L2 checkpoint");
1755
1756 let mut cursor = store
1757 .mvcc_reclaim_cursor(crate::storage::cache::blob::INTERNAL_L2_COLLECTION.to_string())
1758 .expect("L2 reclaim cursor");
1759 for _ in 0..64 {
1760 store
1761 .reclaim_mvcc_tick(
1762 &mut cursor,
1763 &runtime.snapshot_manager(),
1764 runtime.mvcc_vacuum_cutoff_xid(),
1765 64,
1766 8,
1767 )
1768 .expect("L2 MVCC reclaim");
1769 if cursor.finished() {
1770 break;
1771 }
1772 }
1773 assert!(cursor.finished());
1774 drop(cursor);
1775 for _ in 0..3 {
1776 runtime.checkpoint().expect("publish reusable L2 pages");
1777 }
1778 let free_before_reuse = pager.free_page_count().expect("free pages");
1779
1780 for id in 0..8 {
1781 runtime
1782 .result_blob_cache()
1783 .put(
1784 "reuse",
1785 format!("replacement-{id}"),
1786 crate::storage::cache::BlobCachePut::new(payload.clone()).with_policy(
1787 crate::storage::cache::BlobCachePolicy::default()
1788 .expires_at_unix_ms(u64::MAX)
1789 .l1_admission(crate::storage::cache::L1Admission::Never),
1790 ),
1791 )
1792 .expect("replacement L2 payload");
1793 }
1794 runtime.checkpoint().expect("replacement L2 checkpoint");
1795 let pages_after_reuse = pager.page_count().expect("page count after reuse");
1796 assert!(
1797 pages_after_reuse <= initial_pages.saturating_add(16),
1798 "expired L2 pages were not reused: initial={initial_pages}, free_before={free_before_reuse}, after={pages_after_reuse}"
1799 );
1800 }
1801
1802 #[test]
1803 fn zero_cache_namespace_limit_is_rejected_on_boot() {
1804 let directory = tempfile::tempdir().expect("cache config fixture");
1805 let options = RedDBOptions::persistent(directory.path().join("cache-config-zero.rdb"));
1806 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1807 runtime
1808 .db()
1809 .store()
1810 .set_config_tree("cache.blob.max_namespaces", &crate::json!(0));
1811 runtime.flush().expect("persist cache config");
1812 drop(runtime);
1813
1814 let error = match RedDBRuntime::with_options(options) {
1815 Ok(_) => panic!("zero limit must fail boot"),
1816 Err(error) => error,
1817 };
1818 assert!(error
1819 .to_string()
1820 .contains("cache.blob.max_namespaces must be greater than zero"));
1821 }
1822
1823 #[test]
1824 fn zero_cache_namespace_memory_limit_is_rejected_on_boot() {
1825 let directory = tempfile::tempdir().expect("cache config fixture");
1826 let options = RedDBOptions::persistent(directory.path().join("cache-bytes-zero.rdb"));
1827 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1828 runtime
1829 .db()
1830 .store()
1831 .set_config_tree("cache.blob.namespace_bytes_max", &crate::json!(0));
1832 runtime.flush().expect("persist cache config");
1833 drop(runtime);
1834
1835 let error = match RedDBRuntime::with_options(options) {
1836 Ok(_) => panic!("zero namespace memory limit must fail boot"),
1837 Err(error) => error,
1838 };
1839 assert!(error
1840 .to_string()
1841 .contains("cache.blob.namespace_bytes_max must be greater than zero"));
1842 }
1843}
1844
1845#[cfg(test)]
1846mod xid_resume_tests {
1847 use super::*;
1848 use reddb_engine::storage::collection_read::{CollectionCursor, CollectionRead, EncodedRecord};
1849
1850 struct Unreadable;
1851
1852 fn unavailable() -> crate::storage::StoreError {
1853 crate::storage::StoreError::Io(std::io::Error::other("XID bootstrap read a base payload"))
1854 }
1855
1856 impl CollectionRead for Unreadable {
1857 fn get_record(
1858 &self,
1859 id: crate::storage::EntityId,
1860 _: usize,
1861 ) -> Result<Option<EncodedRecord>, crate::storage::StoreError> {
1862 if id.raw() == 999 {
1863 Ok(None)
1864 } else {
1865 Err(unavailable())
1866 }
1867 }
1868 fn cursor(&self) -> Result<Box<dyn CollectionCursor + '_>, crate::storage::StoreError> {
1869 Err(unavailable())
1870 }
1871 fn owned_cursor(&self) -> Result<Box<dyn CollectionCursor>, crate::storage::StoreError> {
1872 Err(unavailable())
1873 }
1874 }
1875
1876 #[test]
1877 fn xid_resume_survives_pending_checkpoint_process_exit() {
1878 const FIXTURE: &str = "REDDB_XID_RESUME_CRASH_FIXTURE";
1879 let open = |path: std::path::PathBuf| {
1880 let options = RedDBOptions::persistent(path)
1881 .with_memory_budget(64 * 1024 * 1024)
1882 .with_storage_profile(
1883 crate::storage::profile::StorageDeployPreset::PrimaryReplicaDev.selection(),
1884 )
1885 .expect("profile");
1886 crate::storage::RedDB::with_disk_residency_for_qualification(|| {
1887 RedDBRuntime::with_options(options)
1888 })
1889 .expect("crash recovery runtime")
1890 };
1891 if let Some(root) = std::env::var_os(FIXTURE) {
1892 let root = std::path::PathBuf::from(root);
1893 assert_eq!(
1894 std::fs::read_to_string(root.join("owned-fixture")).expect("owned fixture"),
1895 "xid-resume-crash-v1"
1896 );
1897 let runtime = open(root.join("crash.rdb"));
1898 for sql in [
1899 "CREATE TABLE crash_rows (id INTEGER, payload TEXT)",
1900 "INSERT INTO crash_rows (id, payload) VALUES (1, 'committed')",
1901 "BEGIN",
1902 "UPDATE crash_rows SET payload = 'pending' WHERE id = 1",
1903 "INSERT INTO crash_rows (id, payload) VALUES (2, 'pending')",
1904 ] {
1905 runtime.execute_query(sql).expect("crash fixture statement");
1906 }
1907 runtime
1908 .flush()
1909 .expect("checkpoint with pending transaction");
1910 std::fs::write(
1911 root.join("checkpoint-complete"),
1912 "pending transaction remains open",
1913 )
1914 .expect("acknowledged fixture checkpoint");
1915 std::process::exit(0);
1918 }
1919 let directory = tempfile::tempdir().expect("owned crash fixture");
1920 std::fs::write(
1921 directory.path().join("owned-fixture"),
1922 "xid-resume-crash-v1",
1923 )
1924 .expect("ownership marker");
1925 let child = std::process::Command::new(std::env::current_exe().expect("test executable"))
1926 .args([
1927 "--exact",
1928 "runtime::impl_lifecycle::xid_resume_tests::xid_resume_survives_pending_checkpoint_process_exit",
1929 "--test-threads=1",
1930 ])
1931 .env(FIXTURE, directory.path())
1932 .output()
1933 .expect("crashing owned child");
1934 assert!(
1935 child.status.success(),
1936 "{}",
1937 String::from_utf8_lossy(&child.stderr)
1938 );
1939 assert!(directory.path().join("checkpoint-complete").is_file());
1940 let path = directory.path().join("crash.rdb");
1941 assert!(path.is_file());
1942 let runtime = open(path.clone());
1943 let result = runtime
1944 .execute_query("SELECT id, payload FROM crash_rows ORDER BY id")
1945 .expect("committed preimage");
1946 assert_eq!(
1947 result.result.records.len(),
1948 1,
1949 "pending insert must stay invisible"
1950 );
1951 assert_eq!(result.result.records[0].get("id"), Some(&Value::Integer(1)));
1952 assert_eq!(
1953 result.result.records[0].get("payload"),
1954 Some(&Value::text("committed"))
1955 );
1956 runtime
1957 .execute_query("INSERT INTO crash_rows (id, payload) VALUES (3, 'after')")
1958 .expect("new writer after recovery");
1959 runtime.flush().expect("recovered checkpoint");
1960 drop(runtime);
1961 let reopened = open(path);
1962 let result = reopened
1963 .execute_query("SELECT id FROM crash_rows ORDER BY id")
1964 .expect("second recovery");
1965 assert_eq!(result.result.records.len(), 2);
1966 assert_eq!(result.result.records[0].get("id"), Some(&Value::Integer(1)));
1967 assert_eq!(result.result.records[1].get("id"), Some(&Value::Integer(3)));
1968 }
1969
1970 #[test]
1971 fn xid_resume_skips_known_base_payloads_but_rejects_an_unproven_root() {
1972 let directory = tempfile::tempdir().expect("resume fixture");
1973 let options = RedDBOptions::persistent(directory.path().join("resume.rdb"))
1974 .with_memory_budget(64 * 1024 * 1024)
1975 .with_storage_profile(
1976 crate::storage::profile::StorageDeployPreset::PrimaryReplicaDev.selection(),
1977 )
1978 .expect("profile");
1979 let runtime = crate::storage::RedDB::with_disk_residency_for_qualification(|| {
1980 RedDBRuntime::with_options(options)
1981 })
1982 .expect("runtime");
1983 runtime
1984 .execute_query("CREATE TABLE resume_rows (id INTEGER)")
1985 .expect("table");
1986 runtime
1987 .execute_query("INSERT INTO resume_rows (id) VALUES (1)")
1988 .expect("row");
1989 runtime.flush().expect("publish complete XID summary");
1990 let store = runtime.db().store();
1991 assert!(store.startup_xid_high_water_mark().is_some());
1992 let root = store
1993 .collection_root_page("resume_rows")
1994 .expect("published root");
1995 let manager = store.get_collection("resume_rows").expect("manager");
1996 for (candidate, proven) in [(root, true), (root + 1, false)] {
1997 manager.install_backing(
1998 Arc::new(Unreadable),
1999 candidate,
2000 store.format_version(),
2001 4096,
2002 1,
2003 );
2004 let mut overlay = crate::storage::UnifiedEntity::table_row(
2005 crate::storage::EntityId::new(999),
2006 "resume_rows",
2007 999,
2008 vec![Value::Integer(2)],
2009 );
2010 overlay.xmin = 500_000;
2011 overlay.xmax = 600_000;
2012 manager.insert(overlay).expect("resident WAL overlay");
2013 let result = runtime.rehydrate_snapshot_xid_floor();
2014 assert_eq!(result.is_ok(), proven, "root={candidate}: {result:?}");
2015 if proven {
2016 assert!(runtime.snapshot_manager().peek_next_xid() > 600_000);
2017 }
2018 }
2019 }
2020}