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 inherited_replication_lsn = if runtime.inner.db.replication.is_some() {
685 let promotion_lsn = runtime
686 .primary_replica_timeline_history()?
687 .and_then(|history| history.entries.last().map(|entry| entry.fork_lsn))
688 .unwrap_or(0);
689 runtime
690 .config_u64("replication.last_applied_lsn", 0)
691 .max(promotion_lsn)
692 } else {
693 0
694 };
695 let restored_cdc_lsn = runtime
696 .inner
697 .db
698 .replication
699 .as_ref()
700 .map(|repl| {
701 repl.logical_wal_spool
702 .as_ref()
703 .map(|spool| spool.current_lsn())
704 .unwrap_or(0)
705 })
706 .unwrap_or(0)
707 .max(runtime.config_u64("timeline.last_archived_lsn", 0))
708 .max(inherited_replication_lsn);
709 runtime.inner.cdc.set_current_lsn(restored_cdc_lsn);
710 runtime
711 .inner
712 .logical_allocated_lsn
713 .store(restored_cdc_lsn, std::sync::atomic::Ordering::Release);
714 runtime.rehydrate_snapshot_xid_floor()?;
715 assert!(runtime
716 .inner
717 .db
718 .runtime
719 .set(super::WeakRuntime(Arc::downgrade(&runtime.inner)))
720 .is_ok());
721 assert!(
722 runtime
723 .inner
724 .db
725 .store()
726 .snapshot_manager
727 .set(runtime.snapshot_manager())
728 .is_ok(),
729 "a new runtime owns the store transaction outcomes"
730 );
731 runtime
732 .bootstrap_system_keyed_collections()
733 .map_err(|err| RedDBError::Internal(format!("bootstrap system collections: {err}")))?;
734 runtime.rehydrate_declared_column_schemas();
735 super::control_event_index::prepare(&runtime)?;
738 super::queue_storage_index::prepare(&runtime)?;
739 runtime.rehydrate_runtime_index_registry()?;
740 if runtime
741 .db()
742 .store()
743 .config()
744 .resident_memory_bytes_max
745 .is_some()
746 {
747 runtime
748 .db()
749 .store()
750 .checkpoint_missing_secondary_indices()?;
751 }
752 runtime.rehydrate_kv_tags()?;
753 runtime
754 .load_probabilistic_state()
755 .map_err(|err| RedDBError::Internal(format!("load probabilistic state: {err}")))?;
756
757 runtime.rehydrate_tenant_tables();
761 runtime.rehydrate_materialized_view_descriptors()?;
766 runtime.recover_store_forks()?;
767 if let Some(repl) = &runtime.inner.db.replication {
768 repl.wal_buffer.set_current_lsn(restored_cdc_lsn);
769 if let Some(spool) = &repl.logical_wal_spool {
770 spool.restore_lsn_floor(restored_cdc_lsn);
771 }
772 }
773 runtime.recover_transactional_replication()?;
774
775 {
777 let sys = SystemInfo::collect();
778 let store = runtime.inner.db.store();
779 store.with_boot_system_info_update(|| {
780 store.set_config_tree(
781 "system",
782 &crate::serde_json::json!({
783 "pid": sys.pid,
784 "cpu_cores": sys.cpu_cores,
785 "total_memory_bytes": sys.total_memory_bytes,
786 "available_memory_bytes": sys.available_memory_bytes,
787 "os": sys.os,
788 "arch": sys.arch,
789 "hostname": sys.hostname,
790 "started_at": SystemTime::now()
791 .duration_since(UNIX_EPOCH)
792 .unwrap_or_default()
793 .as_millis() as u64
794 }),
795 )
796 });
797
798 let store = runtime.inner.db.store();
800 if store
801 .get_collection("red.config")
802 .map(|m| m.query_all(|_| true).len())
803 .unwrap_or(0)
804 <= 10
805 {
806 store.set_config_tree("ai", &crate::json!({
807 "default": crate::json!({
808 "provider": "openai",
809 "model": crate::ai::DEFAULT_OPENAI_PROMPT_MODEL
810 }),
811 "max_embedding_inputs": 256,
812 "max_prompt_batch": 256,
813 "timeout": crate::json!({ "connect_secs": 10, "read_secs": 90, "write_secs": 30 })
814 }));
815 store.set_config_tree(
816 "server",
817 &crate::json!({
818 "max_scan_limit": 1000,
819 "max_body_size": 1048576,
820 "read_timeout_ms": 5000,
821 "write_timeout_ms": 5000
822 }),
823 );
824 store.set_config_tree(
825 "storage",
826 &crate::json!({
827 "page_size": 4096,
828 "page_cache_capacity": 100000,
829 "auto_checkpoint_pages": 1000,
830 "snapshot_retention": 16,
831 "verify_checksums": true,
832 "segment": crate::json!({
833 "max_entities": 100000,
834 "max_bytes": 268435456_u64,
835 "compression_level": 6
836 }),
837 "hnsw": crate::json!({ "m": 16, "ef_construction": 100, "ef_search": 50 }),
838 "ivf": crate::json!({ "n_lists": 100, "n_probes": 10 }),
839 "bm25": crate::json!({ "k1": 1.2, "b": 0.75 })
840 }),
841 );
842 store.set_config_tree(
843 "search",
844 &crate::json!({
845 "rag": crate::json!({
846 "max_chunks_per_source": 10,
847 "max_total_chunks": 25,
848 "similarity_threshold": 0.8,
849 "graph_depth": 2,
850 "min_relevance": 0.3
851 }),
852 "fusion": crate::json!({
853 "vector_weight": 0.5,
854 "graph_weight": 0.3,
855 "table_weight": 0.2,
856 "dedup_threshold": 0.85
857 })
858 }),
859 );
860 store.set_config_tree(
861 "auth",
862 &crate::json!({
863 "enabled": false,
864 "session_ttl_secs": 3600,
865 "require_auth": false
866 }),
867 );
868 store.set_config_tree(
869 "query",
870 &crate::json!({
871 "connection_pool": crate::json!({ "max_connections": 64, "max_idle": 16 }),
872 "max_recursion_depth": 1000
873 }),
874 );
875 store.set_config_tree(
876 "indexes",
877 &crate::json!({
878 "auto_select": true,
879 "bloom_filter": crate::json!({
880 "enabled": true,
881 "false_positive_rate": 0.01,
882 "prune_on_scan": true
883 }),
884 "hash": crate::json!({ "enabled": true }),
885 "bitmap": crate::json!({ "enabled": true, "max_cardinality": 1000 }),
886 "spatial": crate::json!({ "enabled": true })
887 }),
888 );
889 store.set_config_tree(
890 "probabilistic",
891 &crate::json!({
892 "hll_registers": 16384,
893 "sketch_default_width": 1000,
894 "sketch_default_depth": 5,
895 "filter_default_capacity": 100000
896 }),
897 );
898 store.set_config_tree(
899 "timeseries",
900 &crate::json!({
901 "default_chunk_size": 1024,
902 "compression": crate::json!({
903 "timestamps": "delta_of_delta",
904 "values": "gorilla_xor"
905 }),
906 "default_retention_days": 0
907 }),
908 );
909 store.set_config_tree(
910 "queue",
911 &crate::json!({
912 "default_max_size": 0,
913 "default_max_attempts": 3,
914 "visibility_timeout_ms": 30000,
915 "consumer_idle_timeout_ms": 60000
916 }),
917 );
918 store.set_config_tree(
919 "backup",
920 &crate::json!({
921 "enabled": false,
922 "interval_secs": 3600,
923 "retention_count": 24,
924 "upload": false,
925 "backend": "local"
926 }),
927 );
928 store.set_config_tree(
929 "wal",
930 &crate::json!({
931 "archive": crate::json!({
932 "enabled": false,
933 "retention_hours": 168,
934 "prefix": reddb_file::backup_wal_prefix("")
935 })
936 }),
937 );
938 store.set_config_tree(
939 "cdc",
940 &crate::json!({
941 "enabled": true,
942 "buffer_size": 100000
943 }),
944 );
945 store.set_config_tree(
946 "secret",
947 &crate::json!({
948 "auto_encrypt": true,
949 "auto_decrypt": true
950 }),
951 );
952 }
953
954 crate::runtime::config_matrix::heal_critical_keys(store.as_ref());
961 seed_storage_deploy_config(store.as_ref(), options.storage_profile);
962
963 let lehman_yao = runtime.config_bool("storage.btree.lehman_yao", true);
970 crate::storage::engine::btree::lehman_yao::set_enabled(lehman_yao);
971 if lehman_yao {
972 tracing::info!(
973 "storage.btree.lehman_yao=true — lock-free concurrent descent enabled"
974 );
975 }
976
977 let overlay_path = crate::runtime::config_overlay::config_file_path();
982 let _ =
983 crate::runtime::config_overlay::apply_config_file(store.as_ref(), &overlay_path);
984 }
985
986 {
990 let store = runtime.inner.db.store();
991 for name in crate::application::vcs_collections::ALL {
992 let _ = store.get_or_create_collection(*name);
993 }
994 store.set_config_tree(
997 crate::application::vcs_collections::CONFIG_NAMESPACE,
998 &crate::json!({
999 "default_branch": "main",
1000 "author": crate::json!({
1001 "name": "reddb",
1002 "email": "reddb@localhost"
1003 }),
1004 "protected_branches": crate::json!(["main"]),
1005 "closure": crate::json!({
1006 "enabled": true,
1007 "lazy": true
1008 }),
1009 "merge": crate::json!({
1010 "default_strategy": "auto",
1011 "fast_forward": true
1012 })
1013 }),
1014 );
1015 }
1016
1017 {
1020 let store = runtime.inner.db.store();
1021 for name in crate::application::migration_collections::ALL {
1022 let _ = store.get_or_create_collection(*name);
1023 }
1024 }
1025
1026 let _ = crate::application::topology_collections::ensure(&runtime);
1030
1031 runtime.prepare_paged_counts()?;
1036
1037 if no_auth_boot {
1041 use crate::runtime::control_events::{
1042 ActorRef, ControlEvent, ControlEventCtx, EventKind, Outcome,
1043 };
1044 let ledger = std::sync::Arc::clone(&runtime.inner.control_event_ledger.read());
1045 let ctx = ControlEventCtx {
1046 actor: ActorRef::System("boot"),
1047 scope: None,
1048 request_id: None,
1049 trace_id: None,
1050 };
1051 let event = ControlEvent {
1052 kind: EventKind::AuthDisabled,
1053 outcome: Outcome::Allowed,
1054 action: std::borrow::Cow::Borrowed("auth.disabled"),
1055 resource: Some("server".to_string()),
1056 reason: Some("--no-auth / --dev".to_string()),
1057 matched_policy_id: None,
1058 fields: std::collections::HashMap::new(),
1059 };
1060 if let Err(err) = ledger.emit(&ctx, event) {
1061 tracing::warn!(
1062 target: "reddb::security",
1063 error = %err,
1064 "could not record the auth-disabled boot in the control-event ledger"
1065 );
1066 }
1067 }
1068
1069 {
1083 let weak = Arc::downgrade(&runtime.inner);
1084 runtime
1085 .background_tasks
1086 .as_ref()
1087 .expect("foreground runtime")
1088 .spawn(
1089 std::thread::Builder::new().name("reddb-maintenance".into()),
1090 move |stopped| {
1091 let tick = std::time::Duration::from_millis(200);
1092 let work_interval = std::time::Duration::from_secs(60);
1093 let mut last_work = std::time::Instant::now();
1094 let mut allocator_pressure =
1095 super::allocator_pressure::AllocatorPressure::default();
1096 loop {
1097 std::thread::park_timeout(tick);
1098 if stopped.load(AtomicOrdering::Acquire) {
1099 break;
1100 }
1101 let Some(inner) = weak.upgrade() else {
1102 break;
1105 };
1106 if last_work.elapsed() >= work_interval {
1107 let _stats = inner.db.store().context_index().stats();
1108 last_work = std::time::Instant::now();
1109 }
1110 let rt = RedDBRuntime {
1111 background_tasks: None,
1112 inner,
1113 };
1114 rt.try_reclaim_history_tick();
1115 allocator_pressure.tick();
1116 }
1117 },
1118 );
1119 }
1120
1121 {
1127 let weak = Arc::downgrade(&runtime.inner);
1128 runtime
1129 .background_tasks
1130 .as_ref()
1131 .expect("foreground runtime")
1132 .spawn(
1133 std::thread::Builder::new().name("reddb-cache-sweeper".into()),
1134 move |stopped| loop {
1135 std::thread::park_timeout(std::time::Duration::from_millis(250));
1136 if stopped.load(AtomicOrdering::Acquire) {
1137 break;
1138 }
1139 let Some(inner) = weak.upgrade() else {
1140 break;
1141 };
1142 let _ = crate::storage::cache::sweeper::BlobCacheSweeper::sweep_expired(
1143 &inner.result_blob_cache,
1144 crate::storage::cache::sweeper::SweepLimit::Either {
1145 entries: 512,
1146 millis: 5,
1147 },
1148 );
1149 let rt = RedDBRuntime {
1150 background_tasks: None,
1151 inner,
1152 };
1153 rt.promote_volatile_result_cache_entry();
1154 },
1155 );
1156 }
1157
1158 {
1160 let store = runtime.inner.db.store();
1161 let mut backup_enabled = false;
1162 let mut backup_interval = 3600u64;
1163
1164 if let Some(manager) = store.get_collection("red.config") {
1165 manager.for_each_entity(|entity| {
1166 if let Some(row) = entity.data.as_row() {
1167 let key = row.get_field("key").and_then(|v| match v {
1168 reddb_types::Value::Text(s) => Some(s.as_ref()),
1169 _ => None,
1170 });
1171 let val = row.get_field("value");
1172 if key == Some("backup.enabled") {
1173 backup_enabled = match val {
1174 Some(reddb_types::Value::Boolean(true)) => true,
1175 Some(reddb_types::Value::Text(s)) => &**s == "true",
1176 _ => false,
1177 };
1178 } else if key == Some("backup.interval_secs") {
1179 if let Some(reddb_types::Value::Integer(n)) = val {
1180 backup_interval = *n as u64;
1181 }
1182 }
1183 }
1184 true
1185 });
1186 }
1187
1188 if backup_enabled {
1189 runtime.inner.backup_scheduler.set_interval(backup_interval);
1190 let rt = runtime.clone();
1191 runtime
1192 .inner
1193 .backup_scheduler
1194 .start(move || rt.trigger_backup().map_err(|e| format!("{}", e)));
1195 }
1196 }
1197
1198 {
1200 runtime
1201 .inner
1202 .ec_registry
1203 .load_from_config_store(runtime.inner.db.store().as_ref());
1204 if !runtime.inner.db.options().read_only
1205 && !runtime.inner.ec_registry.async_configs().is_empty()
1206 {
1207 runtime.inner.ec_worker.start(
1208 Arc::clone(&runtime.inner.ec_registry),
1209 Arc::clone(&runtime.inner.db.store()),
1210 );
1211 }
1212 }
1213
1214 if let (true, crate::replication::ReplicationRole::Replica { primary_addr }) = (
1215 runtime.inner.db.options().replica_loop_enabled
1216 && !runtime.inner.db.options().read_only,
1217 runtime.inner.db.options().replication.role.clone(),
1218 ) {
1219 let rt = runtime.clone();
1220 std::thread::Builder::new()
1221 .name("reddb-replica".into())
1222 .spawn(move || rt.run_replica_loop(primary_addr))
1223 .ok();
1224 }
1225
1226 runtime.inner.lifecycle.mark_ready();
1231
1232 #[cfg(not(test))]
1233 if runtime
1234 .db()
1235 .store()
1236 .config()
1237 .resident_memory_bytes_max
1238 .is_some()
1239 && !runtime.db().options().read_only
1240 {
1241 runtime.start_count_projection_worker();
1242 }
1243
1244 {
1253 let weak_inner = Arc::downgrade(&runtime.inner);
1254 runtime
1255 .background_tasks
1256 .as_ref()
1257 .expect("foreground runtime")
1258 .spawn(
1259 std::thread::Builder::new()
1260 .name("reddb-mv-scheduler".into())
1261 .stack_size(8 * 1024 * 1024),
1266 move |stopped| loop {
1267 std::thread::park_timeout(std::time::Duration::from_millis(50));
1268 if stopped.load(AtomicOrdering::Acquire) {
1269 break;
1270 }
1271 let Some(inner) = weak_inner.upgrade() else {
1272 break;
1273 };
1274 let rt = RedDBRuntime {
1275 background_tasks: None,
1276 inner,
1277 };
1278 rt.refresh_due_materialized_views();
1279 },
1280 );
1281 }
1282
1283 if !runtime.write_gate().is_read_only() {
1293 let weak_inner = Arc::downgrade(&runtime.inner);
1294 runtime
1295 .background_tasks
1296 .as_ref()
1297 .expect("foreground runtime")
1298 .spawn(
1299 std::thread::Builder::new().name("reddb-retention-sweeper".into()),
1300 move |stopped| loop {
1301 std::thread::park_timeout(std::time::Duration::from_millis(500));
1302 if stopped.load(AtomicOrdering::Acquire) {
1303 break;
1304 }
1305 let Some(inner) = weak_inner.upgrade() else {
1306 break;
1307 };
1308 let rt = RedDBRuntime {
1309 background_tasks: None,
1310 inner,
1311 };
1312 rt.sweep_retention_tick(
1313 crate::runtime::retention_sweeper::DEFAULT_SWEEPER_BATCH,
1314 );
1315 rt.sweep_individual_ttl_tick(
1316 crate::runtime::retention_sweeper::DEFAULT_SWEEPER_BATCH,
1317 );
1318 },
1319 );
1320 }
1321
1322 runtime
1328 .inner
1329 .db
1330 .store()
1331 .register_entity_id(crate::storage::EntityId::new(
1332 crate::storage::FIRST_USER_ENTITY_ID - 1,
1333 ));
1334
1335 Ok(runtime)
1336 }
1337
1338 fn rehydrate_snapshot_xid_floor(&self) -> RedDBResult<()> {
1339 let store = self.inner.db.store();
1340 let snapshot_manager = self.inner.transaction_state.snapshot_manager();
1341 let base_high_water_mark = store.startup_xid_high_water_mark();
1342 if let Some(high_water_mark) = base_high_water_mark {
1343 snapshot_manager.observe_committed_xid(high_water_mark);
1344 }
1345 for collection in store.list_collections() {
1346 let Some(manager) = store.get_collection(&collection) else {
1347 continue;
1348 };
1349 let observe = |entity: &crate::storage::UnifiedEntity| {
1350 snapshot_manager.observe_committed_xid(entity.xmin);
1351 snapshot_manager.observe_committed_xid(entity.xmax);
1352 true
1353 };
1354 if base_high_water_mark.is_some() {
1355 manager.for_each_entity(observe);
1358 } else {
1359 manager.try_for_each_entity(usize::MAX, observe)?;
1362 }
1363 }
1364 if base_high_water_mark.is_none() {
1365 store.remember_startup_xid_high_water_mark(
1366 snapshot_manager.peek_next_xid().saturating_sub(1),
1367 );
1368 }
1369 Ok(())
1370 }
1371
1372 pub(crate) fn ensure_materialized_view_backing(&self, name: &str) -> RedDBResult<()> {
1385 let store = self.inner.db.store();
1386 let mut changed = false;
1387 if store.get_collection(name).is_none() {
1388 store.get_or_create_collection(name);
1389 changed = true;
1390 }
1391 if self.inner.db.collection_contract(name).is_none() {
1392 self.inner
1393 .db
1394 .save_collection_contract(system_keyed_collection_contract(
1395 name,
1396 crate::catalog::CollectionModel::Table,
1397 ))
1398 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1399 changed = true;
1400 }
1401 if changed {
1402 self.inner
1403 .db
1404 .persist_metadata()
1405 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1406 }
1407 Ok(())
1408 }
1409
1410 pub(crate) fn drop_materialized_view_backing(&self, name: &str) -> RedDBResult<()> {
1415 let store = self.inner.db.store();
1416 if store.get_collection(name).is_none() {
1417 return Ok(());
1418 }
1419 store
1420 .drop_collection(name)
1421 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1422 if self.inner.db.collection_contract(name).is_some() {
1425 self.inner
1426 .db
1427 .remove_collection_contract(name)
1428 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1429 }
1430 self.invalidate_result_cache();
1431 self.inner
1432 .db
1433 .persist_metadata()
1434 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1435 Ok(())
1436 }
1437
1438 fn bootstrap_system_keyed_collections(&self) -> RedDBResult<()> {
1439 let mut changed = false;
1440 for (name, model) in [
1441 ("red.config", crate::catalog::CollectionModel::Config),
1442 ("red.vault", crate::catalog::CollectionModel::Vault),
1443 (
1447 crate::runtime::continuous_materialized_view::CATALOG_COLLECTION,
1448 crate::catalog::CollectionModel::Config,
1449 ),
1450 ] {
1451 if self.inner.db.store().get_collection(name).is_none() {
1452 self.inner.db.store().get_or_create_collection(name);
1453 changed = true;
1454 }
1455 if self.inner.db.collection_contract(name).is_none() {
1456 let contract = system_keyed_collection_contract(name, model);
1457 if self.inner.db.options().read_only {
1458 self.inner.db.cache_read_only_bootstrap_contract(contract);
1459 } else {
1460 self.inner
1461 .db
1462 .save_collection_contract(contract)
1463 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1464 }
1465 changed = true;
1466 }
1467 }
1468 if changed {
1469 self.inner
1470 .db
1471 .persist_metadata()
1472 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1473 }
1474 Ok(())
1475 }
1476
1477 pub fn db(&self) -> Arc<RedDB> {
1478 Arc::clone(&self.inner.db)
1479 }
1480
1481 pub fn index_store_ref(&self) -> &super::index_store::IndexStore {
1486 &self.inner.index_store
1487 }
1488
1489 pub(crate) fn schema_vocabulary_apply(
1494 &self,
1495 event: crate::runtime::schema_vocabulary::DdlEvent,
1496 ) {
1497 self.inner.schema_vocabulary.write().on_ddl(event.clone());
1498 self.schedule_collection_description_enrichment(&event);
1499 }
1500
1501 pub(crate) fn schema_vocabulary_sync_descriptions(&self, collection: &str) {
1502 let Some(contract) = self.inner.db.collection_contract(collection) else {
1503 return;
1504 };
1505 let columns = contract
1506 .declared_columns
1507 .iter()
1508 .filter_map(|column| {
1509 column
1510 .description
1511 .as_ref()
1512 .map(|description| (column.name.clone(), description.clone()))
1513 })
1514 .collect::<Vec<_>>();
1515 self.inner.schema_vocabulary.write().update_descriptions(
1516 collection,
1517 contract.description.as_deref(),
1518 &columns,
1519 );
1520 }
1521
1522 pub fn schema_vocabulary_lookup(
1527 &self,
1528 token: &str,
1529 ) -> Vec<crate::runtime::schema_vocabulary::VocabHit> {
1530 self.inner.schema_vocabulary.read().lookup(token).to_vec()
1531 }
1532
1533 pub fn set_auth_store(&self, store: Arc<crate::auth::store::AuthStore>) {
1537 *self.inner.auth_store.write() = Some(store);
1538 }
1539
1540 pub fn auth_store(&self) -> Option<Arc<crate::auth::store::AuthStore>> {
1543 self.inner.auth_store.read().clone()
1544 }
1545
1546 pub fn set_oauth_validator(&self, validator: Option<Arc<crate::auth::oauth::OAuthValidator>>) {
1550 *self.inner.oauth_validator.write() = validator;
1551 }
1552
1553 pub fn oauth_validator(&self) -> Option<Arc<crate::auth::oauth::OAuthValidator>> {
1557 self.inner.oauth_validator.read().clone()
1558 }
1559
1560 pub fn set_browser_token_authority(
1566 &self,
1567 authority: Option<Arc<crate::auth::browser_token::BrowserTokenAuthority>>,
1568 ) {
1569 *self.inner.browser_token_authority.write() = authority;
1570 }
1571
1572 pub fn browser_token_authority(
1576 &self,
1577 ) -> Option<Arc<crate::auth::browser_token::BrowserTokenAuthority>> {
1578 self.inner.browser_token_authority.read().clone()
1579 }
1580}
1581
1582#[cfg(test)]
1583mod cache_boot_tests {
1584 use super::*;
1585
1586 #[test]
1587 fn persisted_cache_limits_apply_on_next_boot_within_memory_budget() {
1588 let directory = tempfile::tempdir().expect("cache config fixture");
1589 let options = RedDBOptions::persistent(directory.path().join("cache-config.rdb"))
1590 .with_memory_budget(64 * 1024 * 1024);
1591 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1592 runtime.db().store().set_config_tree(
1593 "cache.blob",
1594 &crate::json!({
1595 "l1_bytes_max": 1024 * 1024,
1596 "l2_bytes_max": 2 * 1024 * 1024,
1597 "namespace_bytes_max": 256 * 1024,
1598 "max_namespaces": 7
1599 }),
1600 );
1601 runtime.flush().expect("persist cache config");
1602 drop(runtime);
1603
1604 let reopened = RedDBRuntime::with_options(options).expect("configured runtime");
1605 let stats = reopened.result_blob_cache().stats();
1606 assert_eq!(stats.l1_bytes_max(), 1024 * 1024);
1607 assert_eq!(stats.l2_bytes_max(), 2 * 1024 * 1024);
1608 assert_eq!(stats.namespace_bytes_max(), 256 * 1024);
1609 assert_eq!(stats.max_namespaces(), 7);
1610 }
1611
1612 #[test]
1613 fn single_file_l2_survives_runtime_restart_without_sidecar() {
1614 let directory = tempfile::tempdir().expect("single-file L2 fixture");
1615 let path = directory.path().join("cache-l2.rdb");
1616 let options = RedDBOptions::persistent(&path);
1617 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1618 runtime
1619 .result_blob_cache()
1620 .put(
1621 "tenant",
1622 "key",
1623 crate::storage::cache::BlobCachePut::new(b"durable".to_vec()).with_policy(
1624 crate::storage::cache::BlobCachePolicy::default()
1625 .l1_admission(crate::storage::cache::L1Admission::Never),
1626 ),
1627 )
1628 .expect("write internal L2");
1629 assert!(runtime.result_blob_cache().l2_path().is_none());
1630 runtime.flush().expect("checkpoint internal L2");
1631 drop(runtime);
1632
1633 let reopened = RedDBRuntime::with_options(options).expect("reopen runtime");
1634 assert_eq!(
1635 reopened
1636 .result_blob_cache()
1637 .get("tenant", "key")
1638 .expect("L2 hit after restart")
1639 .bytes()
1640 .as_ref(),
1641 b"durable"
1642 );
1643 drop(reopened);
1644 let siblings: Vec<_> = std::fs::read_dir(directory.path())
1645 .expect("list fixture")
1646 .filter_map(Result::ok)
1647 .map(|entry| entry.file_name().to_string_lossy().into_owned())
1648 .collect();
1649 assert_eq!(siblings, vec!["cache-l2.rdb"]);
1650 }
1651
1652 #[test]
1653 fn single_file_l2_deadline_survives_restart_and_sweeps_atomically() {
1654 let directory = tempfile::tempdir().expect("single-file L2 TTL fixture");
1655 let path = directory.path().join("cache-l2-ttl.rdb");
1656 let options = RedDBOptions::persistent(&path);
1657 let mut runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1658 drop(runtime.background_tasks.take());
1659 runtime
1660 .result_blob_cache()
1661 .put(
1662 "tenant",
1663 "expired",
1664 crate::storage::cache::BlobCachePut::new(b"expired".to_vec()).with_policy(
1665 crate::storage::cache::BlobCachePolicy::default()
1666 .expires_at_unix_ms(1)
1667 .l1_admission(crate::storage::cache::L1Admission::Never),
1668 ),
1669 )
1670 .expect("write expiring L2 entry");
1671 runtime.flush().expect("checkpoint L2 deadline");
1672 drop(runtime);
1673
1674 let mut reopened = RedDBRuntime::with_options(options).expect("reopen runtime");
1675 drop(reopened.background_tasks.take());
1676 let manager = reopened
1677 .db()
1678 .store()
1679 .get_collection(crate::storage::cache::blob::INTERNAL_L2_COLLECTION)
1680 .expect("L2 collection");
1681 assert_eq!(manager.scheduled_metadata_expiry_count(), 1);
1682 let bytes_before = reopened.result_blob_cache().stats().l2_bytes_in_use();
1683 assert!(bytes_before > 0);
1684
1685 assert_eq!(reopened.sweep_individual_ttl_tick_at(64, u64::MAX), 0);
1688 assert_eq!(
1689 reopened.result_blob_cache().stats().l2_bytes_in_use(),
1690 bytes_before
1691 );
1692 let report = crate::storage::cache::BlobCacheSweeper::sweep_expired(
1693 reopened.result_blob_cache(),
1694 crate::storage::cache::SweepLimit::Entries(1),
1695 );
1696 assert_eq!(report.entries_evicted, 1);
1697 assert_eq!(reopened.result_blob_cache().stats().l2_bytes_in_use(), 0);
1698 assert_eq!(manager.scheduled_metadata_expiry_count(), 0);
1699 }
1700
1701 #[test]
1702 fn expired_l2_payload_pages_are_reused() {
1703 let directory = tempfile::tempdir().expect("L2 page reuse fixture");
1704 let path = directory.path().join("cache-l2-reuse.rdb");
1705 let options = RedDBOptions::persistent(&path)
1706 .with_memory_budget(32 * 1024 * 1024)
1707 .with_bounded_disk_residency(true);
1708 let mut runtime = RedDBRuntime::with_options(options.clone()).expect("runtime");
1709 drop(runtime.background_tasks.take());
1710 let mut state = 0x9e37_79b9_7f4a_7c15u64;
1711 let payload: Vec<u8> = std::iter::repeat_with(|| {
1712 state ^= state << 13;
1713 state ^= state >> 7;
1714 state ^= state << 17;
1715 state.to_le_bytes()[0]
1716 })
1717 .take(64 * 1024)
1718 .collect();
1719 for id in 0..8 {
1720 runtime
1721 .result_blob_cache()
1722 .put(
1723 "reuse",
1724 format!("expired-{id}"),
1725 crate::storage::cache::BlobCachePut::new(payload.clone()).with_policy(
1726 crate::storage::cache::BlobCachePolicy::default()
1727 .expires_at_unix_ms(1)
1728 .l1_admission(crate::storage::cache::L1Admission::Never),
1729 ),
1730 )
1731 .expect("expired L2 payload");
1732 }
1733 runtime.checkpoint().expect("initial L2 checkpoint");
1734 let initial_pages = runtime
1735 .db()
1736 .store()
1737 .pager()
1738 .expect("pager")
1739 .page_count()
1740 .expect("initial page count");
1741 drop(runtime);
1742
1743 let mut runtime = RedDBRuntime::with_options(options).expect("reopened runtime");
1744 drop(runtime.background_tasks.take());
1745 let store = runtime.db().store();
1746 let pager = store.pager().expect("pager");
1747 let report = crate::storage::cache::BlobCacheSweeper::sweep_expired(
1748 runtime.result_blob_cache(),
1749 crate::storage::cache::SweepLimit::Entries(64),
1750 );
1751 assert_eq!(report.entries_evicted, 8);
1752 runtime.checkpoint().expect("expired L2 checkpoint");
1753
1754 let mut cursor = store
1755 .mvcc_reclaim_cursor(crate::storage::cache::blob::INTERNAL_L2_COLLECTION.to_string())
1756 .expect("L2 reclaim cursor");
1757 for _ in 0..64 {
1758 store
1759 .reclaim_mvcc_tick(
1760 &mut cursor,
1761 &runtime.snapshot_manager(),
1762 runtime.mvcc_vacuum_cutoff_xid(),
1763 64,
1764 8,
1765 )
1766 .expect("L2 MVCC reclaim");
1767 if cursor.finished() {
1768 break;
1769 }
1770 }
1771 assert!(cursor.finished());
1772 drop(cursor);
1773 for _ in 0..3 {
1774 runtime.checkpoint().expect("publish reusable L2 pages");
1775 }
1776 let free_before_reuse = pager.free_page_count().expect("free pages");
1777
1778 for id in 0..8 {
1779 runtime
1780 .result_blob_cache()
1781 .put(
1782 "reuse",
1783 format!("replacement-{id}"),
1784 crate::storage::cache::BlobCachePut::new(payload.clone()).with_policy(
1785 crate::storage::cache::BlobCachePolicy::default()
1786 .expires_at_unix_ms(u64::MAX)
1787 .l1_admission(crate::storage::cache::L1Admission::Never),
1788 ),
1789 )
1790 .expect("replacement L2 payload");
1791 }
1792 runtime.checkpoint().expect("replacement L2 checkpoint");
1793 let pages_after_reuse = pager.page_count().expect("page count after reuse");
1794 assert!(
1795 pages_after_reuse <= initial_pages.saturating_add(16),
1796 "expired L2 pages were not reused: initial={initial_pages}, free_before={free_before_reuse}, after={pages_after_reuse}"
1797 );
1798 }
1799
1800 #[test]
1801 fn zero_cache_namespace_limit_is_rejected_on_boot() {
1802 let directory = tempfile::tempdir().expect("cache config fixture");
1803 let options = RedDBOptions::persistent(directory.path().join("cache-config-zero.rdb"));
1804 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1805 runtime
1806 .db()
1807 .store()
1808 .set_config_tree("cache.blob.max_namespaces", &crate::json!(0));
1809 runtime.flush().expect("persist cache config");
1810 drop(runtime);
1811
1812 let error = match RedDBRuntime::with_options(options) {
1813 Ok(_) => panic!("zero limit must fail boot"),
1814 Err(error) => error,
1815 };
1816 assert!(error
1817 .to_string()
1818 .contains("cache.blob.max_namespaces must be greater than zero"));
1819 }
1820
1821 #[test]
1822 fn zero_cache_namespace_memory_limit_is_rejected_on_boot() {
1823 let directory = tempfile::tempdir().expect("cache config fixture");
1824 let options = RedDBOptions::persistent(directory.path().join("cache-bytes-zero.rdb"));
1825 let runtime = RedDBRuntime::with_options(options.clone()).expect("initial runtime");
1826 runtime
1827 .db()
1828 .store()
1829 .set_config_tree("cache.blob.namespace_bytes_max", &crate::json!(0));
1830 runtime.flush().expect("persist cache config");
1831 drop(runtime);
1832
1833 let error = match RedDBRuntime::with_options(options) {
1834 Ok(_) => panic!("zero namespace memory limit must fail boot"),
1835 Err(error) => error,
1836 };
1837 assert!(error
1838 .to_string()
1839 .contains("cache.blob.namespace_bytes_max must be greater than zero"));
1840 }
1841}
1842
1843#[cfg(test)]
1844mod xid_resume_tests {
1845 use super::*;
1846 use reddb_engine::storage::collection_read::{CollectionCursor, CollectionRead, EncodedRecord};
1847
1848 struct Unreadable;
1849
1850 fn unavailable() -> crate::storage::StoreError {
1851 crate::storage::StoreError::Io(std::io::Error::other("XID bootstrap read a base payload"))
1852 }
1853
1854 impl CollectionRead for Unreadable {
1855 fn get_record(
1856 &self,
1857 id: crate::storage::EntityId,
1858 _: usize,
1859 ) -> Result<Option<EncodedRecord>, crate::storage::StoreError> {
1860 if id.raw() == 999 {
1861 Ok(None)
1862 } else {
1863 Err(unavailable())
1864 }
1865 }
1866 fn cursor(&self) -> Result<Box<dyn CollectionCursor + '_>, crate::storage::StoreError> {
1867 Err(unavailable())
1868 }
1869 fn owned_cursor(&self) -> Result<Box<dyn CollectionCursor>, crate::storage::StoreError> {
1870 Err(unavailable())
1871 }
1872 }
1873
1874 #[test]
1875 fn xid_resume_survives_pending_checkpoint_process_exit() {
1876 const FIXTURE: &str = "REDDB_XID_RESUME_CRASH_FIXTURE";
1877 let open = |path: std::path::PathBuf| {
1878 let options = RedDBOptions::persistent(path)
1879 .with_memory_budget(64 * 1024 * 1024)
1880 .with_storage_profile(
1881 crate::storage::profile::StorageDeployPreset::PrimaryReplicaDev.selection(),
1882 )
1883 .expect("profile");
1884 crate::storage::RedDB::with_disk_residency_for_qualification(|| {
1885 RedDBRuntime::with_options(options)
1886 })
1887 .expect("crash recovery runtime")
1888 };
1889 if let Some(root) = std::env::var_os(FIXTURE) {
1890 let root = std::path::PathBuf::from(root);
1891 assert_eq!(
1892 std::fs::read_to_string(root.join("owned-fixture")).expect("owned fixture"),
1893 "xid-resume-crash-v1"
1894 );
1895 let runtime = open(root.join("crash.rdb"));
1896 for sql in [
1897 "CREATE TABLE crash_rows (id INTEGER, payload TEXT)",
1898 "INSERT INTO crash_rows (id, payload) VALUES (1, 'committed')",
1899 "BEGIN",
1900 "UPDATE crash_rows SET payload = 'pending' WHERE id = 1",
1901 "INSERT INTO crash_rows (id, payload) VALUES (2, 'pending')",
1902 ] {
1903 runtime.execute_query(sql).expect("crash fixture statement");
1904 }
1905 runtime
1906 .flush()
1907 .expect("checkpoint with pending transaction");
1908 std::fs::write(
1909 root.join("checkpoint-complete"),
1910 "pending transaction remains open",
1911 )
1912 .expect("acknowledged fixture checkpoint");
1913 std::process::exit(0);
1916 }
1917 let directory = tempfile::tempdir().expect("owned crash fixture");
1918 std::fs::write(
1919 directory.path().join("owned-fixture"),
1920 "xid-resume-crash-v1",
1921 )
1922 .expect("ownership marker");
1923 let child = std::process::Command::new(std::env::current_exe().expect("test executable"))
1924 .args([
1925 "--exact",
1926 "runtime::impl_lifecycle::xid_resume_tests::xid_resume_survives_pending_checkpoint_process_exit",
1927 "--test-threads=1",
1928 ])
1929 .env(FIXTURE, directory.path())
1930 .output()
1931 .expect("crashing owned child");
1932 assert!(
1933 child.status.success(),
1934 "{}",
1935 String::from_utf8_lossy(&child.stderr)
1936 );
1937 assert!(directory.path().join("checkpoint-complete").is_file());
1938 let path = directory.path().join("crash.rdb");
1939 assert!(path.is_file());
1940 let runtime = open(path.clone());
1941 let result = runtime
1942 .execute_query("SELECT id, payload FROM crash_rows ORDER BY id")
1943 .expect("committed preimage");
1944 assert_eq!(
1945 result.result.records.len(),
1946 1,
1947 "pending insert must stay invisible"
1948 );
1949 assert_eq!(result.result.records[0].get("id"), Some(&Value::Integer(1)));
1950 assert_eq!(
1951 result.result.records[0].get("payload"),
1952 Some(&Value::text("committed"))
1953 );
1954 runtime
1955 .execute_query("INSERT INTO crash_rows (id, payload) VALUES (3, 'after')")
1956 .expect("new writer after recovery");
1957 runtime.flush().expect("recovered checkpoint");
1958 drop(runtime);
1959 let reopened = open(path);
1960 let result = reopened
1961 .execute_query("SELECT id FROM crash_rows ORDER BY id")
1962 .expect("second recovery");
1963 assert_eq!(result.result.records.len(), 2);
1964 assert_eq!(result.result.records[0].get("id"), Some(&Value::Integer(1)));
1965 assert_eq!(result.result.records[1].get("id"), Some(&Value::Integer(3)));
1966 }
1967
1968 #[test]
1969 fn xid_resume_skips_known_base_payloads_but_rejects_an_unproven_root() {
1970 let directory = tempfile::tempdir().expect("resume fixture");
1971 let options = RedDBOptions::persistent(directory.path().join("resume.rdb"))
1972 .with_memory_budget(64 * 1024 * 1024)
1973 .with_storage_profile(
1974 crate::storage::profile::StorageDeployPreset::PrimaryReplicaDev.selection(),
1975 )
1976 .expect("profile");
1977 let runtime = crate::storage::RedDB::with_disk_residency_for_qualification(|| {
1978 RedDBRuntime::with_options(options)
1979 })
1980 .expect("runtime");
1981 runtime
1982 .execute_query("CREATE TABLE resume_rows (id INTEGER)")
1983 .expect("table");
1984 runtime
1985 .execute_query("INSERT INTO resume_rows (id) VALUES (1)")
1986 .expect("row");
1987 runtime.flush().expect("publish complete XID summary");
1988 let store = runtime.db().store();
1989 assert!(store.startup_xid_high_water_mark().is_some());
1990 let root = store
1991 .collection_root_page("resume_rows")
1992 .expect("published root");
1993 let manager = store.get_collection("resume_rows").expect("manager");
1994 for (candidate, proven) in [(root, true), (root + 1, false)] {
1995 manager.install_backing(
1996 Arc::new(Unreadable),
1997 candidate,
1998 store.format_version(),
1999 4096,
2000 1,
2001 );
2002 let mut overlay = crate::storage::UnifiedEntity::table_row(
2003 crate::storage::EntityId::new(999),
2004 "resume_rows",
2005 999,
2006 vec![Value::Integer(2)],
2007 );
2008 overlay.xmin = 500_000;
2009 overlay.xmax = 600_000;
2010 manager.insert(overlay).expect("resident WAL overlay");
2011 let result = runtime.rehydrate_snapshot_xid_floor();
2012 assert_eq!(result.is_ok(), proven, "root={candidate}: {result:?}");
2013 if proven {
2014 assert!(runtime.snapshot_manager().peek_next_xid() > 600_000);
2015 }
2016 }
2017 }
2018}