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