Skip to main content

clt_database/mvcc/persistent_storage/
mod.rs

1use crate::io::FileSyncType;
2use crate::storage::encryption::EncryptionContext;
3use crate::storage::sqlite3_ondisk::DatabaseHeader;
4use crate::sync::atomic::{AtomicI64, AtomicU64, Ordering};
5use crate::sync::Arc;
6use crate::sync::RwLock;
7use crate::turso_assert;
8use std::fmt::Debug;
9
10pub mod logical_log;
11use crate::mvcc::database::{LogRecord, RowVersion};
12use crate::mvcc::persistent_storage::logical_log::{
13    serialize_header_entry, serialize_op_entry, LogicalLog, OnSerializationComplete,
14    DEFAULT_LOG_CHECKPOINT_THRESHOLD,
15};
16use crate::{CheckpointResult, Completion, File, LimboError, Result};
17
18pub trait DurableStorage: Send + Sync + Debug {
19    /// Append one row-version op to `log_record`'s payload buffer, in the
20    /// on-disk wire format used by the logical log. Updates `op_count`.
21    fn serialize_row_version(
22        &self,
23        log_record: &mut LogRecord,
24        row_version: &RowVersion,
25        portable_extension: Option<&[u8]>,
26    ) -> Result<()>;
27
28    /// Append a `DatabaseHeader` op to `log_record`'s payload buffer.
29    fn serialize_database_header(
30        &self,
31        log_record: &mut LogRecord,
32        header: &DatabaseHeader,
33    ) -> Result<()>;
34
35    /// Write a transaction to the logical log without advancing the writer offset.
36    ///
37    /// If `on_serialization_complete` is provided, it is called with shared
38    /// ownership of the framed bytes and the running CRC after framing but
39    /// before the disk write. The callback runs while the internal write lock
40    /// is held, so it should be fast.
41    fn log_tx(
42        &self,
43        m: LogRecord,
44        on_serialization_complete: OnSerializationComplete<'_>,
45    ) -> Result<(Completion, u64)>;
46
47    /// If `m` needs a logical-log header upgrade before it can be appended,
48    /// start that write and return its completion. Callers must wait for this
49    /// completion and then call `log_tx`.
50    fn upgrade_header_for_log_tx(&self, m: &LogRecord) -> Result<Option<Completion>>;
51
52    fn sync(&self, sync_type: FileSyncType) -> Result<Completion>;
53
54    /// Called after a logical-log write completed successfully, before the
55    /// transaction is made visible by advancing the logical-log offset.
56    ///
57    /// Implementations may return a completion for any additional durability
58    /// work that must finish before commit publication.
59    fn on_log_write_complete(&self) -> Result<Completion> {
60        Ok(Completion::new_yield())
61    }
62
63    /// Persist the current logical-log header to durable storage.
64    ///
65    /// This is used by MVCC recovery/checkpoint flows. Keeping this in the trait avoids
66    /// reaching into concrete storage internals.
67    fn update_header(&self) -> Result<Completion>;
68
69    /// Truncate the logical log, discarding frames at or below
70    /// `checkpointed_through_ts` (the checkpoint's published boundary). Frames
71    /// above the boundary (uncheckpointed concurrent commits) are preserved.
72    fn truncate(&self, checkpointed_through_ts: u64) -> Result<Completion>;
73
74    /// Reset the logical log to a fresh header-only file.
75    ///
76    /// Used after an external database restore so future MVCC recovery starts
77    /// from the restored image instead of replaying stale local log frames.
78    fn reset_to_fresh_header(&self) -> Result<Completion>;
79    fn get_logical_log_file(&self) -> Arc<dyn File>;
80    fn logical_log_offset(&self) -> u64;
81    fn should_checkpoint(&self) -> bool;
82    /// Set the checkpoint threshold in bytes of logical-log data written.
83    /// A negative value disables automatic checkpointing.
84    fn set_checkpoint_threshold(&self, threshold: i64);
85    fn checkpoint_threshold(&self) -> i64;
86    fn advance_logical_log_offset_after_success(&self, bytes: u64) -> Result<()>;
87    fn discard_pending_log_write(&self) -> Result<()> {
88        Ok(())
89    }
90    fn restore_logical_log_state_after_recovery(&self, offset: u64, running_crc: u32);
91
92    /// Set the in-memory log header from a previously-read on-disk header.
93    ///
94    /// Called during recovery to seed the CRC state from the header's salt.
95    fn set_header(&self, header: logical_log::LogHeader);
96
97    /// Called when a checkpoint begins, before any rows are written to the B-tree.
98    fn on_checkpoint_start(&self) -> Result<()> {
99        Ok(())
100    }
101
102    /// Called after the checkpoint has fully completed: rows are flushed, WAL is
103    /// truncated, and the logical log is reset.
104    fn on_checkpoint_end(&self, _result: Result<&CheckpointResult>) -> Result<()> {
105        Ok(())
106    }
107
108    fn encryption_ctx(&self) -> Option<EncryptionContext> {
109        None
110    }
111}
112
113pub struct Storage {
114    pub logical_log: RwLock<LogicalLog>,
115    /// Shadowed from LogicalLog::offset for lock-free should_checkpoint() reads.
116    log_offset: AtomicU64,
117    checkpoint_threshold: AtomicI64,
118}
119
120impl Storage {
121    pub fn new(
122        file: Arc<dyn File>,
123        io: Arc<dyn crate::IO>,
124        encryption_ctx: Option<EncryptionContext>,
125    ) -> Self {
126        Self {
127            logical_log: RwLock::new(LogicalLog::new(file, io, encryption_ctx)),
128            log_offset: AtomicU64::new(0),
129            checkpoint_threshold: AtomicI64::new(DEFAULT_LOG_CHECKPOINT_THRESHOLD),
130        }
131    }
132
133    /// Update the shadow offset to stay in sync with LogicalLog::offset.
134    /// Called after any operation that mutates the canonical offset under the write lock.
135    #[inline(always)]
136    fn shadow_offset_store(&self, value: u64) {
137        self.log_offset.store(value, Ordering::Relaxed);
138    }
139
140    #[inline(always)]
141    fn shadow_offset_advance(&self, bytes: u64) {
142        self.log_offset.fetch_add(bytes, Ordering::Relaxed);
143    }
144}
145
146impl DurableStorage for Storage {
147    fn serialize_row_version(
148        &self,
149        log_record: &mut LogRecord,
150        row_version: &RowVersion,
151        portable_extension: Option<&[u8]>,
152    ) -> Result<()> {
153        serialize_op_entry(&mut log_record.buf, row_version, portable_extension)?;
154        log_record.op_count = log_record.op_count.checked_add(1).ok_or_else(|| {
155            LimboError::InternalError("logical log op_count exceeds u32".to_string())
156        })?;
157        Ok(())
158    }
159
160    fn serialize_database_header(
161        &self,
162        log_record: &mut LogRecord,
163        header: &DatabaseHeader,
164    ) -> Result<()> {
165        turso_assert!(
166            !log_record.has_header,
167            "DatabaseHeader op appended more than once to a single LogRecord"
168        );
169        serialize_header_entry(&mut log_record.buf, header);
170        log_record.has_header = true;
171        log_record.op_count = log_record.op_count.checked_add(1).ok_or_else(|| {
172            LimboError::InternalError("logical log op_count exceeds u32".to_string())
173        })?;
174        Ok(())
175    }
176
177    fn log_tx(
178        &self,
179        m: LogRecord,
180        on_serialization_complete: OnSerializationComplete<'_>,
181    ) -> Result<(Completion, u64)> {
182        self.logical_log
183            .write()
184            .log_tx_deferred_offset(m, on_serialization_complete)
185    }
186
187    fn upgrade_header_for_log_tx(&self, m: &LogRecord) -> Result<Option<Completion>> {
188        self.logical_log.write().upgrade_header_for_log_tx(m)
189    }
190
191    fn sync(&self, sync_type: FileSyncType) -> Result<Completion> {
192        self.logical_log.write().sync(sync_type)
193    }
194
195    fn update_header(&self) -> Result<Completion> {
196        self.logical_log.write().update_header()
197    }
198
199    fn truncate(&self, checkpointed_through_ts: u64) -> Result<Completion> {
200        let mut log = self.logical_log.write();
201        let c = log.truncate(checkpointed_through_ts)?;
202        // Shadow the log's actual offset: 0 if it truncated, unchanged if it
203        // skipped (uncheckpointed frames remain), so should_checkpoint() stays
204        // accurate.
205        let new_offset = log.offset;
206        drop(log);
207        self.shadow_offset_store(new_offset);
208        Ok(c)
209    }
210
211    fn reset_to_fresh_header(&self) -> Result<Completion> {
212        let c = self.logical_log.write().reset_to_fresh_header()?;
213        self.shadow_offset_store(0);
214        Ok(c)
215    }
216
217    fn get_logical_log_file(&self) -> Arc<dyn File> {
218        self.logical_log.read().file.clone()
219    }
220
221    fn logical_log_offset(&self) -> u64 {
222        self.log_offset.load(Ordering::Relaxed)
223    }
224
225    fn encryption_ctx(&self) -> Option<EncryptionContext> {
226        self.logical_log.read().encryption_ctx().cloned()
227    }
228
229    /// Lock-free: reads shadowed atomics only.
230    fn should_checkpoint(&self) -> bool {
231        let threshold = self.checkpoint_threshold.load(Ordering::Relaxed);
232        if threshold < 0 {
233            return false;
234        }
235        self.log_offset.load(Ordering::Relaxed) >= threshold as u64
236    }
237
238    fn set_checkpoint_threshold(&self, threshold: i64) {
239        self.checkpoint_threshold
240            .store(threshold, Ordering::Relaxed);
241    }
242
243    fn checkpoint_threshold(&self) -> i64 {
244        self.checkpoint_threshold.load(Ordering::Relaxed)
245    }
246
247    fn advance_logical_log_offset_after_success(&self, bytes: u64) -> Result<()> {
248        self.logical_log.write().advance_offset_after_success(bytes);
249        self.shadow_offset_advance(bytes);
250        Ok(())
251    }
252
253    fn restore_logical_log_state_after_recovery(&self, offset: u64, running_crc: u32) {
254        let mut log = self.logical_log.write();
255        log.offset = offset;
256        log.running_crc = running_crc;
257        self.shadow_offset_store(offset);
258    }
259
260    fn set_header(&self, header: logical_log::LogHeader) {
261        self.logical_log.write().set_header(header);
262    }
263}
264
265impl Debug for Storage {
266    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
267        write!(f, "LogicalLog {{ logical_log }}")
268    }
269}