1use super::impl_config_secret::seed_storage_deploy_config;
13use super::*;
14
15pub(crate) fn view_records_to_entities(
24 table: &str,
25 records: &[crate::storage::query::unified::UnifiedRecord],
26) -> Vec<crate::storage::UnifiedEntity> {
27 use std::collections::HashMap;
28 let table_arc: std::sync::Arc<str> = std::sync::Arc::from(table);
29 let mut out = Vec::with_capacity(records.len());
30 for record in records {
31 let mut named: HashMap<String, reddb_types::Value> = HashMap::new();
32 for (name, value) in record.iter_fields() {
33 named.insert(name.to_string(), value.clone());
34 }
35 let entity = crate::storage::UnifiedEntity::new(
36 crate::storage::EntityId::new(0),
37 crate::storage::EntityKind::TableRow {
38 table: std::sync::Arc::clone(&table_arc),
39 row_id: 0,
40 },
41 crate::storage::EntityData::Row(crate::storage::RowData {
42 columns: Vec::new(),
43 named: Some(named),
44 schema: None,
45 }),
46 );
47 out.push(entity);
48 }
49 out
50}
51
52fn system_keyed_collection_contract(
53 name: &str,
54 model: crate::catalog::CollectionModel,
55) -> crate::physical::CollectionContract {
56 let now = crate::utils::now_unix_millis() as u128;
57 crate::physical::CollectionContract {
58 name: name.to_string(),
59 declared_model: model,
60 schema_mode: crate::catalog::SchemaMode::Dynamic,
61 origin: crate::physical::ContractOrigin::Implicit,
62 version: 1,
63 created_at_unix_ms: now,
64 updated_at_unix_ms: now,
65 default_ttl_ms: None,
66 vector_dimension: None,
67 vector_metric: None,
68 context_index_fields: Vec::new(),
69 declared_columns: Vec::new(),
70 table_def: None,
71 timestamps_enabled: false,
72 context_index_enabled: false,
73 metrics_raw_retention_ms: None,
74 metrics_rollup_policies: Vec::new(),
75 metrics_tenant_identity: None,
76 metrics_namespace: None,
77 append_only: false,
78 subscriptions: Vec::new(),
79 analytics_config: Vec::new(),
80 session_key: None,
81 session_gap_ms: None,
82 retention_duration_ms: None,
83 analytical_storage: None,
84
85 ai_policy: None,
86 }
87}
88
89pub(crate) fn table_row_index_fields(
90 entity: &crate::storage::unified::entity::UnifiedEntity,
91) -> Vec<(String, reddb_types::Value)> {
92 let crate::storage::EntityData::Row(row) = &entity.data else {
93 return Vec::new();
94 };
95 if let Some(named) = &row.named {
96 return named
97 .iter()
98 .map(|(name, value)| (name.clone(), value.clone()))
99 .collect();
100 }
101 if let Some(schema) = &row.schema {
102 return schema
103 .iter()
104 .zip(row.columns.iter())
105 .map(|(name, value)| (name.clone(), value.clone()))
106 .collect();
107 }
108 Vec::new()
109}
110
111impl RedDBRuntime {
112 pub fn in_memory() -> RedDBResult<Self> {
113 Self::with_options(RedDBOptions::in_memory())
114 }
115
116 pub fn flush(&self) -> RedDBResult<()> {
117 self.inner
118 .db
119 .flush()
120 .map_err(|err| RedDBError::Internal(err.to_string()))
121 }
122
123 pub fn lock_manager(&self) -> std::sync::Arc<crate::runtime::lock_manager::LockManager> {
127 self.inner.lock_manager.clone()
128 }
129
130 pub fn config_registry(&self) -> std::sync::Arc<crate::auth::registry::ConfigRegistry> {
132 self.inner.config_registry.clone()
133 }
134
135 pub fn query_audit(&self) -> std::sync::Arc<crate::runtime::query_audit::QueryAuditStream> {
136 self.inner.query_audit.clone()
137 }
138
139 pub fn control_events_require_persistence(&self) -> bool {
140 self.inner.control_event_config.require_persistence()
141 }
142
143 pub fn control_event_config(&self) -> crate::runtime::control_events::ControlEventConfig {
144 self.inner.control_event_config
145 }
146
147 pub fn control_event_ledger(
148 &self,
149 ) -> Arc<dyn crate::runtime::control_events::ControlEventLedger> {
150 self.inner.control_event_ledger.read().clone()
151 }
152
153 #[doc(hidden)]
154 pub fn replace_control_event_ledger_for_tests(
155 &self,
156 ledger: Arc<dyn crate::runtime::control_events::ControlEventLedger>,
157 ) {
158 *self.inner.control_event_ledger.write() = ledger;
159 }
160
161 #[inline(never)]
162 pub fn with_options(options: RedDBOptions) -> RedDBResult<Self> {
163 Self::with_pool(options, ConnectionPoolConfig::default())
164 }
165
166 pub fn memory_budget(&self) -> crate::storage::memory_budget::MemoryBudget {
170 self.inner.memory_budget
171 }
172
173 pub fn memory_accounting(&self) -> &crate::storage::memory_pools::MemoryAccounting {
177 &self.inner.memory_accounting
178 }
179
180 pub fn with_pool(
181 options: RedDBOptions,
182 pool_config: ConnectionPoolConfig,
183 ) -> RedDBResult<Self> {
184 Self::with_pool_and_limits(
185 options,
186 pool_config,
187 super::resource_limits::ResourceLimits::from_env(),
188 )
189 }
190
191 pub(super) fn with_pool_and_limits(
192 options: RedDBOptions,
193 pool_config: ConnectionPoolConfig,
194 resource_limits: super::resource_limits::ResourceLimits,
195 ) -> RedDBResult<Self> {
196 options
197 .replication
198 .validate_failover_profile()
199 .map_err(RedDBError::InvalidConfig)?;
200 let boot_open_start_ms = std::time::SystemTime::now()
208 .duration_since(std::time::UNIX_EPOCH)
209 .map(|d| d.as_millis() as u64)
210 .unwrap_or(0);
211 let memory_budget = crate::storage::memory_budget::resolve_for_boot(
215 options.storage_profile.deploy_profile,
216 options.memory_budget_bytes,
217 )
218 .map_err(|err| RedDBError::InvalidConfig(err.to_string()))?;
219 crate::storage::memory_budget::log_resolved_once(&memory_budget);
220 let memory_shares = crate::storage::memory_pools::BudgetShares::resolve(
224 memory_budget,
225 options.storage_profile.deploy_profile,
226 );
227 memory_shares.log_once();
228 let embedded_single_file = options.storage_profile.deploy_profile
229 == crate::storage::DeployProfile::Embedded
230 && options.storage_profile.packaging == crate::storage::StoragePackaging::SingleFile;
231 let db = Arc::new(RedDB::open_with_options(&options).map_err(|err| {
232 match err.downcast::<crate::storage::StoreError>() {
233 Ok(store_err) => RedDBError::from(*store_err),
234 Err(err) => RedDBError::Internal(err.to_string()),
235 }
236 })?);
237 let result_blob_cache_config = if embedded_single_file {
241 crate::storage::cache::BlobCacheConfig::default()
242 } else {
243 crate::storage::cache::BlobCacheConfig::default().with_l2_path(
244 reddb_file::layout::result_cache_l2_path(
245 &options.resolved_path(reddb_file::default_database_path()),
246 ),
247 )
248 }
249 .with_l1_bytes_max(memory_shares.blob_cache_l1_bytes());
250 let result_blob_cache =
251 crate::storage::cache::BlobCache::open_with_l2(result_blob_cache_config).map_err(
252 |err| RedDBError::Internal(format!("open result Blob Cache L2 failed: {err:?}")),
253 )?;
254 let storage_ready_ms = std::time::SystemTime::now()
255 .duration_since(std::time::UNIX_EPOCH)
256 .map(|d| d.as_millis() as u64)
257 .unwrap_or(0);
258
259 let no_auth_boot = crate::service_cli::no_auth_active(&options);
260 let runtime = Self {
261 background_tasks: Some(Arc::new(background_tasks::RuntimeBackgroundTasks::default())),
262 inner: Arc::new(RuntimeInner {
263 functions: parking_lot::RwLock::new(
264 super::function_catalog::FunctionCatalog::load(&db)?,
265 ),
266 db: db.clone(),
267 layout: PhysicalLayout::from_options(&options),
268 embedded_single_file,
269 memory_budget,
270 memory_accounting: Arc::new(
271 crate::storage::memory_pools::MemoryAccounting::from_shares(
272 memory_budget,
273 memory_shares,
274 ),
275 ),
276 memory_reservations: parking_lot::Mutex::new(
277 super::memory_admission::MemoryReservations::default(),
278 ),
279 mvcc_maintenance: parking_lot::Mutex::new(Default::default()),
280 indices: IndexCatalog::register_default_vector_graph(
281 options.has_capability(crate::api::Capability::Table),
282 options.has_capability(crate::api::Capability::Graph),
283 ),
284 pool_config,
285 pool: Mutex::new(PoolState::default()),
286 execution_admission: parking_lot::RwLock::new(
287 crate::transport::execution_admission::ExecutionAdmission::for_host(),
288 ),
289 execution_queue_timeout_ms: AtomicU64::new(30_000),
290 started_at_unix_ms: SystemTime::now()
291 .duration_since(UNIX_EPOCH)
292 .unwrap_or_default()
293 .as_millis(),
294 probabilistic: super::probabilistic_store::ProbabilisticStore::new(),
295 index_store: super::index_store::IndexStore::new(),
296 cdc: crate::replication::cdc::CdcBuffer::new(100_000),
297 checkpoint_projection_stats: super::CheckpointProjectionStats::default(),
298 scrub_state: parking_lot::Mutex::new(super::ScrubRuntimeState::default()),
299 checkpoint_columnar_emission_budget_chunks: options
300 .checkpoint_columnar_emission_budget_chunks,
301 columnar_projection_size_floor_rows: options.columnar_projection_size_floor_rows,
302 backup_scheduler: crate::replication::scheduler::BackupScheduler::new(3600),
303 query_cache: parking_lot::RwLock::new(
304 crate::storage::query::planner::cache::PlanCache::new(1000),
305 ),
306 result_cache: parking_lot::RwLock::new((
307 HashMap::new(),
308 std::collections::VecDeque::new(),
309 )),
310 result_blob_cache,
311 result_blob_entries: parking_lot::RwLock::new((
312 HashMap::new(),
313 std::collections::VecDeque::new(),
314 )),
315 ask_answer_cache_entries: parking_lot::RwLock::new((
316 HashSet::new(),
317 std::collections::VecDeque::new(),
318 )),
319 result_cache_shadow_divergences: std::sync::atomic::AtomicU64::new(0),
320 result_cache_hits: std::sync::atomic::AtomicU64::new(0),
321 result_cache_misses: std::sync::atomic::AtomicU64::new(0),
322 result_cache_evictions: std::sync::atomic::AtomicU64::new(0),
323 ask_daily_spend: parking_lot::RwLock::new(HashMap::new()),
324 queue_message_locks: parking_lot::RwLock::new(HashMap::new()),
325 rmw_locks: RmwLockTable::new(),
326 planner_dirty_tables: parking_lot::RwLock::new(HashSet::new()),
327 ec_registry: Arc::new(crate::ec::config::EcRegistry::new()),
328 config_registry: Arc::new(crate::auth::registry::ConfigRegistry::new()),
329 ec_worker: crate::ec::worker::EcWorker::new(),
330 auth_store: parking_lot::RwLock::new(None),
331 oauth_validator: parking_lot::RwLock::new(None),
332 browser_token_authority: parking_lot::RwLock::new(None),
333 views: parking_lot::RwLock::new(HashMap::new()),
334 materialized_views: parking_lot::RwLock::new(
335 crate::storage::cache::result::MaterializedViewCache::new(),
336 ),
337 retention_sweeper: parking_lot::RwLock::new(
338 crate::runtime::retention_sweeper::RetentionSweeperState::new(),
339 ),
340 transaction_state: crate::runtime::transaction_state::TransactionState::new(),
341 env_config_overrides: crate::runtime::config_overlay::collect_env_overrides(),
342 lock_manager: Arc::new({
343 let env = crate::runtime::config_overlay::collect_env_overrides();
348 let timeout_ms = env
349 .get("concurrency.locking.deadlock_timeout_ms")
350 .and_then(|raw| raw.parse::<u64>().ok())
351 .unwrap_or_else(|| {
352 match crate::runtime::config_matrix::default_for(
353 "concurrency.locking.deadlock_timeout_ms",
354 ) {
355 Some(crate::serde_json::Value::Number(n)) => n as u64,
356 _ => 5000,
357 }
358 });
359 let cfg = crate::runtime::lock_manager::LockConfig {
360 default_timeout: std::time::Duration::from_millis(timeout_ms),
361 ..Default::default()
362 };
363 crate::runtime::lock_manager::LockManager::new(cfg)
364 }),
365 rls_policies: parking_lot::RwLock::new(HashMap::new()),
366 rls_enabled_tables: parking_lot::RwLock::new(HashSet::new()),
367 foreign_tables: Arc::new(crate::storage::fdw::ForeignTableRegistry::with_builtins()),
368 pending_tombstones: parking_lot::RwLock::new(HashMap::new()),
369 pending_versioned_updates: parking_lot::RwLock::new(HashMap::new()),
370 pending_queue_dedup: parking_lot::RwLock::new(HashMap::new()),
371 pending_kv_watch_events: parking_lot::RwLock::new(HashMap::new()),
372 pending_vault_writes: parking_lot::RwLock::new(HashMap::new()),
373 pending_store_wal_actions: parking_lot::RwLock::new(HashMap::new()),
374 pending_claim_locks: parking_lot::RwLock::new(HashMap::new()),
375 queue_wait_registry: std::sync::Arc::new(
376 crate::runtime::queue_wait_registry::QueueWaitRegistry::new(),
377 ),
378 pending_queue_wakes: parking_lot::RwLock::new(HashMap::new()),
379 tenant_tables: parking_lot::RwLock::new(HashMap::new()),
380 ddl_epoch: std::sync::atomic::AtomicU64::new(0),
381 write_gate: Arc::new(crate::runtime::write_gate::WriteGate::from_options(
382 &options,
383 )),
384 lifecycle: crate::runtime::lifecycle::Lifecycle::new(),
385 resource_limits,
386 query_budget_slots: AtomicU64::new(0),
387 audit_log: {
388 let data_path = if embedded_single_file {
404 std::env::temp_dir()
405 .join("reddb-embedded-runtime")
406 .join(format!("audit-{}", std::process::id()))
407 } else {
408 options
409 .data_path
410 .clone()
411 .unwrap_or_else(|| std::env::temp_dir().join("reddb"))
412 };
413 let (audit_dest, _) = crate::api::tier_wiring::current_log_destinations();
414 if !matches!(audit_dest, crate::storage::layout::LogDestination::File(_))
415 && (embedded_single_file
416 || options
417 .metadata
418 .contains_key(crate::api::EPHEMERAL_RUNTIME_METADATA_KEY))
419 {
420 let audit_path = reddb_file::layout::sibling_path(
430 &data_path,
431 &reddb_file::layout::sidecar_file_name(&data_path, "audit.log"),
432 );
433 Arc::new(crate::runtime::audit_log::AuditLogger::with_path(
434 audit_path,
435 ))
436 } else {
437 Arc::new(crate::runtime::audit_log::AuditLogger::for_destination(
438 &audit_dest,
439 &data_path,
440 ))
441 }
442 },
443 control_event_ledger: parking_lot::RwLock::new(Arc::new(
444 crate::runtime::control_events::RuntimeLedger::new(db.store()),
445 )),
446 control_event_config: options.control_events,
447 query_audit: Arc::new(crate::runtime::query_audit::QueryAuditStream::new(
448 db.store(),
449 options.query_audit.clone(),
450 )),
451 lease_lifecycle: std::sync::OnceLock::new(),
452 replica_apply_metrics: std::sync::Arc::new(
453 crate::replication::logical::ReplicaApplyMetrics::default(),
454 ),
455 replica_link_metrics: std::sync::Arc::new(
456 crate::replication::reconnect::ReplicaLinkMetrics::default(),
457 ),
458 quota_bucket: crate::runtime::quota_bucket::QuotaBucket::from_env(),
459 schema_vocabulary: parking_lot::RwLock::new(
460 crate::runtime::schema_vocabulary::SchemaVocabulary::new(),
461 ),
462 slow_query_logger: {
463 let fallback_dir = if embedded_single_file {
480 std::env::temp_dir()
481 .join("reddb-embedded-runtime")
482 .join(format!("slow-{}", std::process::id()))
483 } else {
484 options
485 .data_path
486 .as_ref()
487 .and_then(|p| p.parent().map(std::path::PathBuf::from))
488 .unwrap_or_else(|| std::env::temp_dir().join("reddb"))
489 };
490 let threshold_ms = std::env::var("RED_SLOW_QUERY_THRESHOLD_MS")
491 .ok()
492 .and_then(|s| s.parse::<u64>().ok())
493 .unwrap_or(1000);
494 let sample_pct = std::env::var("RED_SLOW_QUERY_SAMPLE_PCT")
495 .ok()
496 .and_then(|s| s.parse::<u8>().ok())
497 .unwrap_or(100);
498 let (_, slow_dest) = crate::api::tier_wiring::current_log_destinations();
499 crate::telemetry::slow_query_logger::SlowQueryLogger::for_destination(
500 &slow_dest,
501 &fallback_dir,
502 threshold_ms,
503 sample_pct,
504 )
505 },
506 slow_query_store: crate::telemetry::slow_query_store::SlowQueryStore::new(
507 crate::telemetry::slow_query_store::DEFAULT_CAP,
508 ),
509 kv_stats: crate::runtime::KvStatsCounters::default(),
510 metrics_ingest_stats: crate::runtime::MetricsIngestCounters::default(),
511 metrics_tenant_activity_stats:
512 crate::runtime::MetricsTenantActivityCounters::default(),
513 claim_telemetry: Arc::new(
514 crate::runtime::claim_telemetry::ClaimTelemetryCounters::default(),
515 ),
516 queue_telemetry: Arc::new(
517 crate::runtime::queue_telemetry::QueueTelemetryCounters::default(),
518 ),
519 query_latency_telemetry: Arc::new(
520 crate::runtime::query_latency_telemetry::QueryLatencyTelemetry::default(),
521 ),
522 occupancy_sampler: Arc::new(
523 crate::runtime::occupancy_sampler::OccupancySampler::new(),
524 ),
525 node_load_telemetry: Arc::new(
526 crate::runtime::node_load_telemetry::NodeLoadTelemetry::default(),
527 ),
528 queue_presence: Arc::new(
529 crate::storage::queue::presence::ConsumerPresenceRegistry::new(),
530 ),
531 vector_introspection: Arc::new(
532 crate::storage::vector::introspection::VectorIntrospectionRegistry::new(),
533 ),
534 kv_tag_index: crate::runtime::KvTagIndex::default(),
535 chain_tip_cache: parking_lot::Mutex::new(HashMap::new()),
536 chain_integrity_broken: parking_lot::Mutex::new(HashMap::new()),
537 integrity_tombstones: parking_lot::Mutex::new(Vec::new()),
538 integrity_tombstones_state: std::sync::atomic::AtomicU8::new(0),
539 }),
540 };
541
542 runtime.install_hash_candidate_admission();
543
544 crate::telemetry::operator_event::install_global_audit_sink(Arc::clone(
550 &runtime.inner.audit_log,
551 ));
552
553 runtime
556 .inner
557 .slow_query_logger
558 .attach_store(Arc::clone(&runtime.inner.slow_query_store));
559
560 runtime
568 .inner
569 .lifecycle
570 .set_restore_started_at_ms(boot_open_start_ms);
571 runtime
572 .inner
573 .lifecycle
574 .set_restore_ready_at_ms(storage_ready_ms);
575 runtime
576 .inner
577 .lifecycle
578 .set_wal_replay_started_at_ms(boot_open_start_ms);
579 runtime
580 .inner
581 .lifecycle
582 .set_wal_replay_ready_at_ms(storage_ready_ms);
583
584 let restored_cdc_lsn = runtime
585 .inner
586 .db
587 .replication
588 .as_ref()
589 .map(|repl| {
590 repl.logical_wal_spool
591 .as_ref()
592 .map(|spool| spool.current_lsn())
593 .unwrap_or(0)
594 })
595 .unwrap_or(0)
596 .max(runtime.config_u64("red.config.timeline.last_archived_lsn", 0));
597 runtime.inner.cdc.set_current_lsn(restored_cdc_lsn);
598 runtime.rehydrate_snapshot_xid_floor()?;
599 assert!(
600 runtime
601 .inner
602 .db
603 .store()
604 .snapshot_manager
605 .set(runtime.snapshot_manager())
606 .is_ok(),
607 "a new runtime owns the store transaction outcomes"
608 );
609 runtime
610 .bootstrap_system_keyed_collections()
611 .map_err(|err| RedDBError::Internal(format!("bootstrap system collections: {err}")))?;
612 runtime.rehydrate_declared_column_schemas();
613 runtime.prepare_paged_counts()?;
614 super::control_event_index::prepare(&runtime)?;
617 super::queue_storage_index::prepare(&runtime)?;
618 runtime.rehydrate_runtime_index_registry()?;
619 if runtime
620 .db()
621 .store()
622 .config()
623 .resident_memory_bytes_max
624 .is_some()
625 {
626 runtime
627 .db()
628 .store()
629 .checkpoint_missing_secondary_indices()?;
630 }
631 runtime.rehydrate_kv_tags()?;
632 runtime
633 .load_probabilistic_state()
634 .map_err(|err| RedDBError::Internal(format!("load probabilistic state: {err}")))?;
635
636 runtime.rehydrate_tenant_tables();
640 runtime.rehydrate_materialized_view_descriptors();
645 if let Some(repl) = &runtime.inner.db.replication {
646 repl.wal_buffer.set_current_lsn(restored_cdc_lsn);
647 }
648
649 {
651 let sys = SystemInfo::collect();
652 runtime.inner.db.store().set_config_tree(
653 "red.system",
654 &crate::serde_json::json!({
655 "pid": sys.pid,
656 "cpu_cores": sys.cpu_cores,
657 "total_memory_bytes": sys.total_memory_bytes,
658 "available_memory_bytes": sys.available_memory_bytes,
659 "os": sys.os,
660 "arch": sys.arch,
661 "hostname": sys.hostname,
662 "started_at": SystemTime::now()
663 .duration_since(UNIX_EPOCH)
664 .unwrap_or_default()
665 .as_millis() as u64
666 }),
667 );
668
669 let store = runtime.inner.db.store();
671 if store
672 .get_collection("red_config")
673 .map(|m| m.query_all(|_| true).len())
674 .unwrap_or(0)
675 <= 10
676 {
677 store.set_config_tree("red.ai", &crate::json!({
678 "default": crate::json!({
679 "provider": "openai",
680 "model": crate::ai::DEFAULT_OPENAI_PROMPT_MODEL
681 }),
682 "max_embedding_inputs": 256,
683 "max_prompt_batch": 256,
684 "timeout": crate::json!({ "connect_secs": 10, "read_secs": 90, "write_secs": 30 })
685 }));
686 store.set_config_tree(
687 "red.server",
688 &crate::json!({
689 "max_scan_limit": 1000,
690 "max_body_size": 1048576,
691 "read_timeout_ms": 5000,
692 "write_timeout_ms": 5000
693 }),
694 );
695 store.set_config_tree(
696 "red.storage",
697 &crate::json!({
698 "page_size": 4096,
699 "page_cache_capacity": 100000,
700 "auto_checkpoint_pages": 1000,
701 "snapshot_retention": 16,
702 "verify_checksums": true,
703 "segment": crate::json!({
704 "max_entities": 100000,
705 "max_bytes": 268435456_u64,
706 "compression_level": 6
707 }),
708 "hnsw": crate::json!({ "m": 16, "ef_construction": 100, "ef_search": 50 }),
709 "ivf": crate::json!({ "n_lists": 100, "n_probes": 10 }),
710 "bm25": crate::json!({ "k1": 1.2, "b": 0.75 })
711 }),
712 );
713 store.set_config_tree(
714 "red.search",
715 &crate::json!({
716 "rag": crate::json!({
717 "max_chunks_per_source": 10,
718 "max_total_chunks": 25,
719 "similarity_threshold": 0.8,
720 "graph_depth": 2,
721 "min_relevance": 0.3
722 }),
723 "fusion": crate::json!({
724 "vector_weight": 0.5,
725 "graph_weight": 0.3,
726 "table_weight": 0.2,
727 "dedup_threshold": 0.85
728 })
729 }),
730 );
731 store.set_config_tree(
732 "red.auth",
733 &crate::json!({
734 "enabled": false,
735 "session_ttl_secs": 3600,
736 "require_auth": false
737 }),
738 );
739 store.set_config_tree(
740 "red.query",
741 &crate::json!({
742 "connection_pool": crate::json!({ "max_connections": 64, "max_idle": 16 }),
743 "max_recursion_depth": 1000
744 }),
745 );
746 store.set_config_tree(
747 "red.indexes",
748 &crate::json!({
749 "auto_select": true,
750 "bloom_filter": crate::json!({
751 "enabled": true,
752 "false_positive_rate": 0.01,
753 "prune_on_scan": true
754 }),
755 "hash": crate::json!({ "enabled": true }),
756 "bitmap": crate::json!({ "enabled": true, "max_cardinality": 1000 }),
757 "spatial": crate::json!({ "enabled": true })
758 }),
759 );
760 store.set_config_tree(
761 "red.probabilistic",
762 &crate::json!({
763 "hll_registers": 16384,
764 "sketch_default_width": 1000,
765 "sketch_default_depth": 5,
766 "filter_default_capacity": 100000
767 }),
768 );
769 store.set_config_tree(
770 "red.timeseries",
771 &crate::json!({
772 "default_chunk_size": 1024,
773 "compression": crate::json!({
774 "timestamps": "delta_of_delta",
775 "values": "gorilla_xor"
776 }),
777 "default_retention_days": 0
778 }),
779 );
780 store.set_config_tree(
781 "red.queue",
782 &crate::json!({
783 "default_max_size": 0,
784 "default_max_attempts": 3,
785 "visibility_timeout_ms": 30000,
786 "consumer_idle_timeout_ms": 60000
787 }),
788 );
789 store.set_config_tree(
790 "red.backup",
791 &crate::json!({
792 "enabled": false,
793 "interval_secs": 3600,
794 "retention_count": 24,
795 "upload": false,
796 "backend": "local"
797 }),
798 );
799 store.set_config_tree(
800 "red.wal",
801 &crate::json!({
802 "archive": crate::json!({
803 "enabled": false,
804 "retention_hours": 168,
805 "prefix": reddb_file::backup_wal_prefix("")
806 })
807 }),
808 );
809 store.set_config_tree(
810 "red.cdc",
811 &crate::json!({
812 "enabled": true,
813 "buffer_size": 100000
814 }),
815 );
816 store.set_config_tree(
817 "red.config.secret",
818 &crate::json!({
819 "auto_encrypt": true,
820 "auto_decrypt": true
821 }),
822 );
823 }
824
825 crate::runtime::config_matrix::heal_critical_keys(store.as_ref());
832 seed_storage_deploy_config(store.as_ref(), options.storage_profile);
833
834 let lehman_yao = runtime.config_bool("storage.btree.lehman_yao", true);
841 crate::storage::engine::btree::lehman_yao::set_enabled(lehman_yao);
842 if lehman_yao {
843 tracing::info!(
844 "storage.btree.lehman_yao=true — lock-free concurrent descent enabled"
845 );
846 }
847
848 let overlay_path = crate::runtime::config_overlay::config_file_path();
853 let _ =
854 crate::runtime::config_overlay::apply_config_file(store.as_ref(), &overlay_path);
855 }
856
857 {
861 let store = runtime.inner.db.store();
862 for name in crate::application::vcs_collections::ALL {
863 let _ = store.get_or_create_collection(*name);
864 }
865 store.set_config_tree(
868 crate::application::vcs_collections::CONFIG_NAMESPACE,
869 &crate::json!({
870 "default_branch": "main",
871 "author": crate::json!({
872 "name": "reddb",
873 "email": "reddb@localhost"
874 }),
875 "protected_branches": crate::json!(["main"]),
876 "closure": crate::json!({
877 "enabled": true,
878 "lazy": true
879 }),
880 "merge": crate::json!({
881 "default_strategy": "auto",
882 "fast_forward": true
883 })
884 }),
885 );
886 }
887
888 {
891 let store = runtime.inner.db.store();
892 for name in crate::application::migration_collections::ALL {
893 let _ = store.get_or_create_collection(*name);
894 }
895 }
896
897 let _ = crate::application::topology_collections::ensure(&runtime);
901
902 runtime
910 .inner
911 .db
912 .store()
913 .register_entity_id(crate::storage::EntityId::new(
914 crate::storage::FIRST_USER_ENTITY_ID - 1,
915 ));
916
917 {
931 let weak = Arc::downgrade(&runtime.inner);
932 runtime
933 .background_tasks
934 .as_ref()
935 .expect("foreground runtime")
936 .spawn(
937 std::thread::Builder::new().name("reddb-maintenance".into()),
938 move |stopped| {
939 let tick = std::time::Duration::from_millis(200);
940 let work_interval = std::time::Duration::from_secs(60);
941 let mut last_work = std::time::Instant::now();
942 let mut allocator_pressure =
943 super::allocator_pressure::AllocatorPressure::default();
944 loop {
945 std::thread::park_timeout(tick);
946 if stopped.load(AtomicOrdering::Acquire) {
947 break;
948 }
949 let Some(inner) = weak.upgrade() else {
950 break;
953 };
954 if last_work.elapsed() >= work_interval {
955 let _stats = inner.db.store().context_index().stats();
956 last_work = std::time::Instant::now();
957 }
958 let rt = RedDBRuntime {
959 background_tasks: None,
960 inner,
961 };
962 rt.try_reclaim_history_tick();
963 allocator_pressure.tick();
964 }
965 },
966 );
967 }
968
969 {
971 let store = runtime.inner.db.store();
972 let mut backup_enabled = false;
973 let mut backup_interval = 3600u64;
974
975 if let Some(manager) = store.get_collection("red_config") {
976 manager.for_each_entity(|entity| {
977 if let Some(row) = entity.data.as_row() {
978 let key = row.get_field("key").and_then(|v| match v {
979 reddb_types::Value::Text(s) => Some(s.as_ref()),
980 _ => None,
981 });
982 let val = row.get_field("value");
983 if key == Some("red.config.backup.enabled") {
984 backup_enabled = match val {
985 Some(reddb_types::Value::Boolean(true)) => true,
986 Some(reddb_types::Value::Text(s)) => &**s == "true",
987 _ => false,
988 };
989 } else if key == Some("red.config.backup.interval_secs") {
990 if let Some(reddb_types::Value::Integer(n)) = val {
991 backup_interval = *n as u64;
992 }
993 }
994 }
995 true
996 });
997 }
998
999 if backup_enabled {
1000 runtime.inner.backup_scheduler.set_interval(backup_interval);
1001 let rt = runtime.clone();
1002 runtime
1003 .inner
1004 .backup_scheduler
1005 .start(move || rt.trigger_backup().map_err(|e| format!("{}", e)));
1006 }
1007 }
1008
1009 {
1011 runtime
1012 .inner
1013 .ec_registry
1014 .load_from_config_store(runtime.inner.db.store().as_ref());
1015 if !runtime.inner.ec_registry.async_configs().is_empty() {
1016 runtime.inner.ec_worker.start(
1017 Arc::clone(&runtime.inner.ec_registry),
1018 Arc::clone(&runtime.inner.db.store()),
1019 );
1020 }
1021 }
1022
1023 if let (true, crate::replication::ReplicationRole::Replica { primary_addr }) = (
1024 runtime.inner.db.options().replica_loop_enabled,
1025 runtime.inner.db.options().replication.role.clone(),
1026 ) {
1027 let rt = runtime.clone();
1028 std::thread::Builder::new()
1029 .name("reddb-replica".into())
1030 .spawn(move || rt.run_replica_loop(primary_addr))
1031 .ok();
1032 }
1033
1034 runtime.inner.lifecycle.mark_ready();
1039
1040 {
1049 let weak_inner = Arc::downgrade(&runtime.inner);
1050 runtime
1051 .background_tasks
1052 .as_ref()
1053 .expect("foreground runtime")
1054 .spawn(
1055 std::thread::Builder::new()
1056 .name("reddb-mv-scheduler".into())
1057 .stack_size(8 * 1024 * 1024),
1062 move |stopped| loop {
1063 std::thread::park_timeout(std::time::Duration::from_millis(50));
1064 if stopped.load(AtomicOrdering::Acquire) {
1065 break;
1066 }
1067 let Some(inner) = weak_inner.upgrade() else {
1068 break;
1069 };
1070 let rt = RedDBRuntime {
1071 background_tasks: None,
1072 inner,
1073 };
1074 rt.refresh_due_materialized_views();
1075 },
1076 );
1077 }
1078
1079 if !runtime.write_gate().is_read_only() {
1089 let weak_inner = Arc::downgrade(&runtime.inner);
1090 runtime
1091 .background_tasks
1092 .as_ref()
1093 .expect("foreground runtime")
1094 .spawn(
1095 std::thread::Builder::new().name("reddb-retention-sweeper".into()),
1096 move |stopped| loop {
1097 std::thread::park_timeout(std::time::Duration::from_millis(500));
1098 if stopped.load(AtomicOrdering::Acquire) {
1099 break;
1100 }
1101 let Some(inner) = weak_inner.upgrade() else {
1102 break;
1103 };
1104 let rt = RedDBRuntime {
1105 background_tasks: None,
1106 inner,
1107 };
1108 rt.sweep_retention_tick(
1109 crate::runtime::retention_sweeper::DEFAULT_SWEEPER_BATCH,
1110 );
1111 },
1112 );
1113 }
1114
1115 if no_auth_boot {
1119 use crate::runtime::control_events::{
1120 ActorRef, ControlEvent, ControlEventCtx, EventKind, Outcome,
1121 };
1122 let ledger = std::sync::Arc::clone(&runtime.inner.control_event_ledger.read());
1123 let ctx = ControlEventCtx {
1124 actor: ActorRef::System("boot"),
1125 scope: None,
1126 request_id: None,
1127 trace_id: None,
1128 };
1129 let event = ControlEvent {
1130 kind: EventKind::AuthDisabled,
1131 outcome: Outcome::Allowed,
1132 action: std::borrow::Cow::Borrowed("auth.disabled"),
1133 resource: Some("server".to_string()),
1134 reason: Some("--no-auth / --dev".to_string()),
1135 matched_policy_id: None,
1136 fields: std::collections::HashMap::new(),
1137 };
1138 if let Err(err) = ledger.emit(&ctx, event) {
1139 tracing::warn!(
1140 target: "reddb::security",
1141 error = %err,
1142 "could not record the auth-disabled boot in the control-event ledger"
1143 );
1144 }
1145 }
1146
1147 Ok(runtime)
1148 }
1149
1150 fn rehydrate_snapshot_xid_floor(&self) -> RedDBResult<()> {
1151 let store = self.inner.db.store();
1152 let snapshot_manager = self.inner.transaction_state.snapshot_manager();
1153 let base_high_water_mark = store.startup_xid_high_water_mark();
1154 if let Some(high_water_mark) = base_high_water_mark {
1155 snapshot_manager.observe_committed_xid(high_water_mark);
1156 }
1157 for collection in store.list_collections() {
1158 let Some(manager) = store.get_collection(&collection) else {
1159 continue;
1160 };
1161 let observe = |entity: &crate::storage::UnifiedEntity| {
1162 snapshot_manager.observe_committed_xid(entity.xmin);
1163 snapshot_manager.observe_committed_xid(entity.xmax);
1164 true
1165 };
1166 if base_high_water_mark.is_some() {
1167 manager.for_each_entity(observe);
1170 } else {
1171 manager.try_for_each_entity(usize::MAX, observe)?;
1174 }
1175 }
1176 if base_high_water_mark.is_none() {
1177 store.remember_startup_xid_high_water_mark(
1178 snapshot_manager.peek_next_xid().saturating_sub(1),
1179 );
1180 }
1181 Ok(())
1182 }
1183
1184 pub(crate) fn ensure_materialized_view_backing(&self, name: &str) -> RedDBResult<()> {
1197 let store = self.inner.db.store();
1198 let mut changed = false;
1199 if store.get_collection(name).is_none() {
1200 store.get_or_create_collection(name);
1201 changed = true;
1202 }
1203 if self.inner.db.collection_contract(name).is_none() {
1204 self.inner
1205 .db
1206 .save_collection_contract(system_keyed_collection_contract(
1207 name,
1208 crate::catalog::CollectionModel::Table,
1209 ))
1210 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1211 changed = true;
1212 }
1213 if changed {
1214 self.inner
1215 .db
1216 .persist_metadata()
1217 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1218 }
1219 Ok(())
1220 }
1221
1222 pub(crate) fn drop_materialized_view_backing(&self, name: &str) -> RedDBResult<()> {
1227 let store = self.inner.db.store();
1228 if store.get_collection(name).is_none() {
1229 return Ok(());
1230 }
1231 store
1232 .drop_collection(name)
1233 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1234 if self.inner.db.collection_contract(name).is_some() {
1237 self.inner
1238 .db
1239 .remove_collection_contract(name)
1240 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1241 }
1242 self.invalidate_result_cache();
1243 self.inner
1244 .db
1245 .persist_metadata()
1246 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1247 Ok(())
1248 }
1249
1250 fn bootstrap_system_keyed_collections(&self) -> RedDBResult<()> {
1251 let mut changed = false;
1252 for (name, model) in [
1253 ("red.config", crate::catalog::CollectionModel::Config),
1254 ("red.vault", crate::catalog::CollectionModel::Vault),
1255 (
1259 crate::runtime::continuous_materialized_view::CATALOG_COLLECTION,
1260 crate::catalog::CollectionModel::Config,
1261 ),
1262 ] {
1263 if self.inner.db.store().get_collection(name).is_none() {
1264 self.inner.db.store().get_or_create_collection(name);
1265 changed = true;
1266 }
1267 if self.inner.db.collection_contract(name).is_none() {
1268 self.inner
1269 .db
1270 .save_collection_contract(system_keyed_collection_contract(name, model))
1271 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1272 changed = true;
1273 }
1274 }
1275 if changed {
1276 self.inner
1277 .db
1278 .persist_metadata()
1279 .map_err(|err| RedDBError::Internal(err.to_string()))?;
1280 }
1281 Ok(())
1282 }
1283
1284 pub fn db(&self) -> Arc<RedDB> {
1285 Arc::clone(&self.inner.db)
1286 }
1287
1288 pub fn index_store_ref(&self) -> &super::index_store::IndexStore {
1293 &self.inner.index_store
1294 }
1295
1296 pub(crate) fn schema_vocabulary_apply(
1301 &self,
1302 event: crate::runtime::schema_vocabulary::DdlEvent,
1303 ) {
1304 self.inner.schema_vocabulary.write().on_ddl(event);
1305 }
1306
1307 pub fn schema_vocabulary_lookup(
1312 &self,
1313 token: &str,
1314 ) -> Vec<crate::runtime::schema_vocabulary::VocabHit> {
1315 self.inner.schema_vocabulary.read().lookup(token).to_vec()
1316 }
1317
1318 pub fn set_auth_store(&self, store: Arc<crate::auth::store::AuthStore>) {
1322 *self.inner.auth_store.write() = Some(store);
1323 }
1324
1325 pub fn auth_store(&self) -> Option<Arc<crate::auth::store::AuthStore>> {
1328 self.inner.auth_store.read().clone()
1329 }
1330
1331 pub fn set_oauth_validator(&self, validator: Option<Arc<crate::auth::oauth::OAuthValidator>>) {
1335 *self.inner.oauth_validator.write() = validator;
1336 }
1337
1338 pub fn oauth_validator(&self) -> Option<Arc<crate::auth::oauth::OAuthValidator>> {
1342 self.inner.oauth_validator.read().clone()
1343 }
1344
1345 pub fn set_browser_token_authority(
1351 &self,
1352 authority: Option<Arc<crate::auth::browser_token::BrowserTokenAuthority>>,
1353 ) {
1354 *self.inner.browser_token_authority.write() = authority;
1355 }
1356
1357 pub fn browser_token_authority(
1361 &self,
1362 ) -> Option<Arc<crate::auth::browser_token::BrowserTokenAuthority>> {
1363 self.inner.browser_token_authority.read().clone()
1364 }
1365}
1366
1367#[cfg(test)]
1368mod xid_resume_tests {
1369 use super::*;
1370 use reddb_engine::storage::collection_read::{CollectionCursor, CollectionRead, EncodedRecord};
1371
1372 struct Unreadable;
1373
1374 fn unavailable() -> crate::storage::StoreError {
1375 crate::storage::StoreError::Io(std::io::Error::other("XID bootstrap read a base payload"))
1376 }
1377
1378 impl CollectionRead for Unreadable {
1379 fn get_record(
1380 &self,
1381 id: crate::storage::EntityId,
1382 _: usize,
1383 ) -> Result<Option<EncodedRecord>, crate::storage::StoreError> {
1384 if id.raw() == 999 {
1385 Ok(None)
1386 } else {
1387 Err(unavailable())
1388 }
1389 }
1390 fn cursor(&self) -> Result<Box<dyn CollectionCursor + '_>, crate::storage::StoreError> {
1391 Err(unavailable())
1392 }
1393 fn owned_cursor(&self) -> Result<Box<dyn CollectionCursor>, crate::storage::StoreError> {
1394 Err(unavailable())
1395 }
1396 }
1397
1398 #[test]
1399 fn xid_resume_survives_pending_checkpoint_process_exit() {
1400 const FIXTURE: &str = "REDDB_XID_RESUME_CRASH_FIXTURE";
1401 let open = |path: std::path::PathBuf| {
1402 let options = RedDBOptions::persistent(path)
1403 .with_memory_budget(64 * 1024 * 1024)
1404 .with_storage_profile(
1405 crate::storage::profile::StorageDeployPreset::PrimaryReplicaDev.selection(),
1406 )
1407 .expect("profile");
1408 crate::storage::RedDB::with_disk_residency_for_qualification(|| {
1409 RedDBRuntime::with_options(options)
1410 })
1411 .expect("crash recovery runtime")
1412 };
1413 if let Some(root) = std::env::var_os(FIXTURE) {
1414 let root = std::path::PathBuf::from(root);
1415 assert_eq!(
1416 std::fs::read_to_string(root.join("owned-fixture")).expect("owned fixture"),
1417 "xid-resume-crash-v1"
1418 );
1419 let runtime = open(root.join("crash.rdb"));
1420 for sql in [
1421 "CREATE TABLE crash_rows (id INTEGER, payload TEXT)",
1422 "INSERT INTO crash_rows (id, payload) VALUES (1, 'committed')",
1423 "BEGIN",
1424 "UPDATE crash_rows SET payload = 'pending' WHERE id = 1",
1425 "INSERT INTO crash_rows (id, payload) VALUES (2, 'pending')",
1426 ] {
1427 runtime.execute_query(sql).expect("crash fixture statement");
1428 }
1429 runtime
1430 .flush()
1431 .expect("checkpoint with pending transaction");
1432 std::fs::write(
1433 root.join("checkpoint-complete"),
1434 "pending transaction remains open",
1435 )
1436 .expect("acknowledged fixture checkpoint");
1437 std::process::exit(0);
1440 }
1441 let directory = tempfile::tempdir().expect("owned crash fixture");
1442 std::fs::write(
1443 directory.path().join("owned-fixture"),
1444 "xid-resume-crash-v1",
1445 )
1446 .expect("ownership marker");
1447 let child = std::process::Command::new(std::env::current_exe().expect("test executable"))
1448 .args([
1449 "--exact",
1450 "runtime::impl_lifecycle::xid_resume_tests::xid_resume_survives_pending_checkpoint_process_exit",
1451 "--test-threads=1",
1452 ])
1453 .env(FIXTURE, directory.path())
1454 .output()
1455 .expect("crashing owned child");
1456 assert!(
1457 child.status.success(),
1458 "{}",
1459 String::from_utf8_lossy(&child.stderr)
1460 );
1461 assert!(directory.path().join("checkpoint-complete").is_file());
1462 let path = directory.path().join("crash.rdb");
1463 assert!(path.is_file());
1464 let runtime = open(path.clone());
1465 let result = runtime
1466 .execute_query("SELECT id, payload FROM crash_rows ORDER BY id")
1467 .expect("committed preimage");
1468 assert_eq!(
1469 result.result.records.len(),
1470 1,
1471 "pending insert must stay invisible"
1472 );
1473 assert_eq!(result.result.records[0].get("id"), Some(&Value::Integer(1)));
1474 assert_eq!(
1475 result.result.records[0].get("payload"),
1476 Some(&Value::text("committed"))
1477 );
1478 runtime
1479 .execute_query("INSERT INTO crash_rows (id, payload) VALUES (3, 'after')")
1480 .expect("new writer after recovery");
1481 runtime.flush().expect("recovered checkpoint");
1482 drop(runtime);
1483 let reopened = open(path);
1484 let result = reopened
1485 .execute_query("SELECT id FROM crash_rows ORDER BY id")
1486 .expect("second recovery");
1487 assert_eq!(result.result.records.len(), 2);
1488 assert_eq!(result.result.records[0].get("id"), Some(&Value::Integer(1)));
1489 assert_eq!(result.result.records[1].get("id"), Some(&Value::Integer(3)));
1490 }
1491
1492 #[test]
1493 fn xid_resume_skips_known_base_payloads_but_rejects_an_unproven_root() {
1494 let directory = tempfile::tempdir().expect("resume fixture");
1495 let options = RedDBOptions::persistent(directory.path().join("resume.rdb"))
1496 .with_memory_budget(64 * 1024 * 1024)
1497 .with_storage_profile(
1498 crate::storage::profile::StorageDeployPreset::PrimaryReplicaDev.selection(),
1499 )
1500 .expect("profile");
1501 let runtime = crate::storage::RedDB::with_disk_residency_for_qualification(|| {
1502 RedDBRuntime::with_options(options)
1503 })
1504 .expect("runtime");
1505 runtime
1506 .execute_query("CREATE TABLE resume_rows (id INTEGER)")
1507 .expect("table");
1508 runtime
1509 .execute_query("INSERT INTO resume_rows (id) VALUES (1)")
1510 .expect("row");
1511 runtime.flush().expect("publish complete XID summary");
1512 let store = runtime.db().store();
1513 assert!(store.startup_xid_high_water_mark().is_some());
1514 let root = store
1515 .collection_root_page("resume_rows")
1516 .expect("published root");
1517 let manager = store.get_collection("resume_rows").expect("manager");
1518 for (candidate, proven) in [(root, true), (root + 1, false)] {
1519 manager.install_backing(
1520 Arc::new(Unreadable),
1521 candidate,
1522 store.format_version(),
1523 4096,
1524 1,
1525 );
1526 let mut overlay = crate::storage::UnifiedEntity::table_row(
1527 crate::storage::EntityId::new(999),
1528 "resume_rows",
1529 999,
1530 vec![Value::Integer(2)],
1531 );
1532 overlay.xmin = 500_000;
1533 overlay.xmax = 600_000;
1534 manager.insert(overlay).expect("resident WAL overlay");
1535 let result = runtime.rehydrate_snapshot_xid_floor();
1536 assert_eq!(result.is_ok(), proven, "root={candidate}: {result:?}");
1537 if proven {
1538 assert!(runtime.snapshot_manager().peek_next_xid() > 600_000);
1539 }
1540 }
1541 }
1542}