1use std::collections::{BTreeMap, HashMap};
22use std::fs::File;
23use std::io::{BufReader, Read};
24use std::path::{Path, PathBuf};
25use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
26use std::sync::Arc;
27
28use parking_lot::RwLock;
29
30use super::context_index::ContextIndex;
31use super::entity::{
32 CrossRef, EmbeddingSlot, EntityData, EntityId, EntityKind, RefType, RowData,
33 TimeSeriesPointKind, UnifiedEntity,
34};
35use super::manager::{ManagerConfig, ManagerStats, SegmentManager};
36use super::metadata::{Metadata, MetadataFilter, MetadataValue};
37use crate::api::{DurabilityMode, GroupCommitOptions};
38use crate::physical::{ManifestEvent, ManifestEventKind};
39use crate::storage::engine::pager::PagerError;
40use crate::storage::engine::{BTree, BTreeError, Pager, PagerConfig, PhysicalFileHeader};
41use crate::storage::primitives::encoding::read_varu64;
42use reddb_types::types::Value;
43
44pub(crate) fn trace_startup_phase(path: &Path, phase: &'static str, started: std::time::Instant) {
45 tracing::debug!(
46 target: "reddb::storage::startup",
47 path = %path.display(),
48 phase,
49 elapsed = ?started.elapsed(),
50 "database startup phase completed"
51 );
52}
53
54pub use reddb_file::{
55 is_supported_store_version, NativeCatalogCollectionSummary, NativeCatalogSummary,
56 NativeExportSummary, NativeManifestEntrySummary, NativeManifestSummary,
57 NativeMetadataStateSummary, NativeRecoverySummary, NativeRegistryIndexSummary,
58 NativeRegistryJobSummary, NativeRegistryProjectionSummary, NativeRegistrySummary,
59 NativeSnapshotSummary, NativeVectorArtifactPageSummary, NativeVectorArtifactSummary,
60 ENTITY_RECORD_MAGIC, METADATA_MAGIC, METADATA_OVERFLOW_MAGIC, NATIVE_BLOB_MAGIC,
61 NATIVE_CATALOG_MAGIC, NATIVE_COLLECTION_ROOTS_MAGIC, NATIVE_MANIFEST_MAGIC,
62 NATIVE_MANIFEST_SAMPLE_LIMIT, NATIVE_METADATA_STATE_MAGIC, NATIVE_RECOVERY_MAGIC,
63 NATIVE_REGISTRY_MAGIC, NATIVE_VECTOR_ARTIFACT_MAGIC, STORE_MAGIC, STORE_VERSION_CURRENT,
64 STORE_VERSION_V1, STORE_VERSION_V11, STORE_VERSION_V12, STORE_VERSION_V2, STORE_VERSION_V3,
65 STORE_VERSION_V4, STORE_VERSION_V5, STORE_VERSION_V6, STORE_VERSION_V7, STORE_VERSION_V8,
66 STORE_VERSION_V9,
67};
68
69#[derive(Debug, Clone, Default, PartialEq, Eq)]
70pub struct MvccVacuumStats {
71 pub scanned_versions: u64,
72 pub retained_versions: u64,
73 pub reclaimed_versions: u64,
74 pub retained_history_versions: u64,
75 pub reclaimed_history_versions: u64,
76 pub retained_tombstones: u64,
77 pub reclaimed_tombstones: u64,
78}
79
80impl MvccVacuumStats {
81 pub fn add(&mut self, other: &Self) {
82 self.scanned_versions += other.scanned_versions;
83 self.retained_versions += other.retained_versions;
84 self.reclaimed_versions += other.reclaimed_versions;
85 self.retained_history_versions += other.retained_history_versions;
86 self.reclaimed_history_versions += other.reclaimed_history_versions;
87 self.retained_tombstones += other.retained_tombstones;
88 self.reclaimed_tombstones += other.reclaimed_tombstones;
89 }
90}
91
92#[derive(Debug, Clone)]
93pub struct NativePhysicalState {
94 pub header: PhysicalFileHeader,
95 pub collection_roots: BTreeMap<String, u64>,
96 pub manifest: Option<NativeManifestSummary>,
97 pub registry: Option<NativeRegistrySummary>,
98 pub recovery: Option<NativeRecoverySummary>,
99 pub catalog: Option<NativeCatalogSummary>,
100 pub metadata_state: Option<NativeMetadataStateSummary>,
101 pub vector_artifact_pages: Option<Vec<NativeVectorArtifactPageSummary>>,
102}
103
104#[derive(Debug, Clone)]
110pub struct UnifiedStoreConfig {
111 pub manager_config: ManagerConfig,
113 pub auto_index_refs: bool,
115 pub auto_index_id: bool,
124 pub max_cross_refs: usize,
126 pub enable_wal: bool,
128 pub durability_mode: DurabilityMode,
130 pub group_commit: GroupCommitOptions,
132 pub data_dir: Option<std::path::PathBuf>,
134 pub embedded_wal_path: Option<std::path::PathBuf>,
136 pub page_cache_slots: Option<usize>,
141 pub resident_memory_bytes_max: Option<u64>,
144}
145
146impl Default for UnifiedStoreConfig {
147 fn default() -> Self {
148 Self {
149 manager_config: ManagerConfig::default(),
150 auto_index_refs: true,
151 auto_index_id: true,
152 max_cross_refs: 1000,
153 enable_wal: false,
154 durability_mode: DurabilityMode::WalDurableGrouped,
157 group_commit: GroupCommitOptions::default(),
158 data_dir: None,
159 embedded_wal_path: None,
160 page_cache_slots: None,
161 resident_memory_bytes_max: None,
162 }
163 }
164}
165
166impl UnifiedStoreConfig {
167 pub fn with_data_dir(mut self, path: impl Into<std::path::PathBuf>) -> Self {
169 self.data_dir = Some(path.into());
170 self
171 }
172
173 pub fn with_wal(mut self) -> Self {
175 self.enable_wal = true;
176 self
177 }
178
179 pub fn with_durability_mode(mut self, mode: DurabilityMode) -> Self {
180 self.durability_mode = mode;
181 self
182 }
183
184 pub fn with_group_commit(mut self, options: GroupCommitOptions) -> Self {
185 self.group_commit = options;
186 self
187 }
188
189 pub fn with_embedded_wal_path(mut self, path: impl Into<std::path::PathBuf>) -> Self {
190 self.embedded_wal_path = Some(path.into());
191 self
192 }
193
194 pub fn with_page_cache_slots(mut self, slots: usize) -> Self {
196 self.page_cache_slots = Some(slots);
197 self
198 }
199
200 pub fn with_max_refs(mut self, max: usize) -> Self {
202 self.max_cross_refs = max;
203 self
204 }
205}
206
207pub use reddb_engine::storage::error::StoreError;
212
213#[derive(Debug, Clone, Default)]
219pub struct StoreStats {
220 pub collection_count: usize,
222 pub total_entities: usize,
224 pub total_memory_bytes: usize,
226 pub collections: HashMap<String, ManagerStats>,
228 pub cross_ref_count: usize,
230}
231
232impl StoreStats {
233 pub fn avg_entities_per_collection(&self) -> f64 {
235 if self.collection_count == 0 {
236 0.0
237 } else {
238 self.total_entities as f64 / self.collection_count as f64
239 }
240 }
241
242 pub fn memory_mb(&self) -> f64 {
244 self.total_memory_bytes as f64 / (1024.0 * 1024.0)
245 }
246}
247
248struct CheckpointVersion {
253 manager: std::sync::Weak<SegmentManager>,
254 segments: Vec<(u64, u64)>,
255 visibility_revision: u64,
256 untracked_visibility_revision: u64,
257 excluded_xids: Arc<std::collections::HashSet<u64>>,
258 pending_vault: Arc<std::collections::HashSet<EntityId>>,
259 format_version: u32,
260 deferred_rows: Option<Arc<Vec<EntityId>>>,
264 has_unprojected_rows: bool,
266}
267
268impl CheckpointVersion {
269 fn remember_deferred_row(&mut self, id: EntityId) {
270 self.has_unprojected_rows = true;
271 let Some(rows) = self.deferred_rows.as_mut() else {
272 return;
273 };
274 if rows.len() == 1024 {
275 self.deferred_rows = None;
276 } else {
277 Arc::make_mut(rows).push(id);
278 }
279 }
280}
281
282pub struct UnifiedStore {
315 resident: reddb_engine::storage::resident_store::ResidentStore,
317 config: UnifiedStoreConfig,
319 format_version: AtomicU32,
321 pager: Option<Arc<Pager>>,
323 db_path: Option<PathBuf>,
325 operational_manifest: parking_lot::Mutex<Option<(PathBuf, reddb_file::OperationalManifest)>>,
326 btree_indices: RwLock<HashMap<String, Arc<BTree>>>,
333 key_indices: RwLock<HashMap<String, PublishedKeyIndex>>,
334 secondary_index_specs: RwLock<secondary_index::SecondaryIndexSpecs>,
335 recovery_xid_summary: RwLock<Option<recovery_xid::RecoveryXidSummary>>,
336 checkpoint_versions: RwLock<HashMap<String, CheckpointVersion>>,
338 checkpoint_durable_rows: RwLock<checkpoint_delta::DurableRows>,
339 retired_btrees: parking_lot::Mutex<Vec<RetiredBTree>>,
342 ownership_catalog_epoch: AtomicU64,
344 ownership_active_epoch: AtomicU64,
345 detached_native_blob_pages: parking_lot::Mutex<std::collections::BTreeSet<u32>>,
347 commit: Option<Arc<StoreCommitCoordinator>>,
349 pub(crate) replayed_turbo_inserts: parking_lot::Mutex<HashMap<String, Vec<(u64, Vec<f32>)>>>,
356 pub(crate) replayed_probabilistic_deltas:
360 parking_lot::Mutex<Vec<(u8, u8, String, Vec<Vec<u8>>)>>,
361 pub(crate) vault_publication_lock: parking_lot::ReentrantMutex<()>,
366 pub(crate) pending_vault_versions: RwLock<std::collections::HashSet<EntityId>>,
368 pub(crate) snapshot_manager:
371 std::sync::OnceLock<Arc<crate::storage::transaction::snapshot::SnapshotManager>>,
372 embedded_commit_session: std::sync::OnceLock<crate::storage::embedded::EmbeddedCommitSession>,
373 #[cfg(test)]
374 pub(crate) test_fail_deferred_wal_append: std::sync::atomic::AtomicBool,
375 #[cfg(test)]
376 pub(crate) test_require_deferred_publication_fence: std::sync::atomic::AtomicBool,
377}
378
379#[derive(Clone)]
380struct PublishedKeyIndex {
381 indexed: bool,
382 source: std::sync::Weak<BTree>,
383 tree: Arc<BTree>,
384 secondary: BTreeMap<String, secondary_index::PublishedSecondaryIndex>,
385}
386
387struct RetiredBTree {
388 tree: Arc<BTree>,
389 published: bool,
390}
391
392mod builder;
393mod checkpoint_cold_append;
394mod checkpoint_delta;
395mod checkpoint_wal;
396mod commit;
397mod impl_entities;
398mod impl_file;
399mod impl_native_a;
400mod impl_native_b;
401mod impl_native_c;
402mod impl_pages;
403mod impl_vault_import;
404mod key_index;
405mod keyed_write;
406mod mvcc_reclaim;
407pub(crate) use mvcc_reclaim::MvccReclaimCursor;
408pub(crate) mod count_index;
409mod native_helpers;
410mod recovery_xid;
411pub mod secondary_index;
412
413pub(crate) use self::commit::{DeferredStoreWalActions, StoreWalCaptureGuard};
414use self::commit::{StoreCommitCoordinator, StoreWalAction};
415use self::native_helpers::*;
416
417#[cfg(test)]
418mod shared_state_tests {
419 use super::*;
420
421 #[test]
422 fn native_mutations_and_shared_reads_use_one_resident_state() {
423 let store = UnifiedStore::new();
424 let resident: &reddb_engine::storage::resident_store::ResidentStore = &store.resident;
425 store.create_collection("nodes").expect("collection");
426 let native = store.get_collection("nodes").expect("native manager");
427 let shared = resident.get_collection("nodes").expect("shared manager");
428 assert!(
429 Arc::ptr_eq(&native, &shared),
430 "one canonical collection owner"
431 );
432 let id = store
433 .insert_auto(
434 "nodes",
435 UnifiedEntity::graph_node(EntityId::new(0), "Person", "person", HashMap::new()),
436 )
437 .expect("native mutation");
438 assert_eq!(id.raw(), 1);
439 assert_eq!(
440 resident.get_any(id).expect("shared read").1.kind,
441 store.get("nodes", id).expect("native read").kind
442 );
443 assert_eq!(
444 resident.lookup_graph_nodes_by_label_in("nodes", "Person"),
445 [id]
446 );
447 assert_eq!(resident.entity_cache_stats().entries, 1);
448 assert!(store.delete("nodes", id).expect("native delete"));
449 assert!(resident.get_any(id).is_none());
450 assert!(resident.lookup_graph_nodes_by_label("Person").is_empty());
451 assert_eq!(resident.entity_cache_stats().entries, 0);
452 store.set_aux_metadata(b"catalog".to_vec());
453 assert_eq!(resident.aux_metadata(), b"catalog");
454 assert_eq!(
455 store.next_entity_id().raw(),
456 2,
457 "native allocator is the shared allocator"
458 );
459 }
460}