Skip to main content

mongreldb_core/
mutable_run.rs

1//! Mutable run tier — the LSM layer between the skip-list memtable and the
2//! immutable `.sr` sorted runs (Phase 11.1).
3//!
4//! A flush drains the live memtable into this in-memory tier instead of
5//! immediately writing a new sorted run. The tier is a [`crate::pma::Pma`] keyed
6//! by the composite `(RowId, Epoch)` version key, so it stays sorted (the
7//! natural order `RunWriter` consumes) and absorbs further flushes in place with
8//! amortized `O(log² n)` inserts — exactly the "cache-oblivious mutable sorted
9//! run" described in §2. Only once the tier crosses a byte watermark does it
10//! spill to an immutable sorted run on disk, coalescing many small flushes into
11//! one larger run (fewer runs ⇒ fewer reader merges ⇒ faster scans).
12//!
13//! MVCC semantics mirror [`crate::memtable::Memtable`]: every version is kept,
14//! keyed by `(RowId, Epoch)`; a snapshot read returns the newest version with
15//! `epoch <= snapshot`. The tier is purely in-memory and rebuilds from WAL
16//! replay on reopen, so it carries no on-disk state of its own.
17
18use crate::epoch::Epoch;
19use crate::memtable::Row;
20use crate::pma::Pma;
21use crate::rowid::RowId;
22use std::collections::BTreeMap;
23use std::sync::Arc;
24
25/// Composite version key — identical to the memtable's, so all versions of one
26/// `RowId` sort contiguously in ascending-epoch order.
27type VersionKey = (RowId, Epoch);
28
29/// The PMA-backed mutable run tier. Holds flushed-but-not-yet-spilled rows in
30/// sorted `(RowId, Epoch)` order.
31#[derive(Clone)]
32struct MutableRunSegment {
33    pma: Pma<VersionKey, Row>,
34    byte_size: u64,
35}
36
37#[derive(Clone)]
38pub struct MutableRun {
39    frozen: Arc<Vec<Arc<MutableRunSegment>>>,
40    active: MutableRunSegment,
41    byte_size: u64,
42}
43
44impl Default for MutableRun {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50impl MutableRun {
51    pub fn new() -> Self {
52        Self {
53            frozen: Arc::new(Vec::new()),
54            active: MutableRunSegment {
55                pma: Pma::new(),
56                byte_size: 0,
57            },
58            byte_size: 0,
59        }
60    }
61
62    /// Fold drained memtable rows (already ascending by `(RowId, Epoch)`) into
63    /// the tier via one bulk merge + re-spread — far cheaper than per-element
64    /// inserts, which would cluster at the tail on sorted input.
65    pub fn insert_many(&mut self, rows: Vec<Row>) {
66        let batch: Vec<(VersionKey, Row)> = rows
67            .into_iter()
68            .map(|r| {
69                let bytes = r.estimated_bytes();
70                self.byte_size = self.byte_size.saturating_add(bytes);
71                self.active.byte_size = self.active.byte_size.saturating_add(bytes);
72                ((r.row_id, r.committed_epoch), r)
73            })
74            .collect();
75        self.active.pma.extend_sorted(batch);
76    }
77
78    /// Number of stored versions.
79    pub fn len(&self) -> usize {
80        self.active.pma.len()
81            + self
82                .frozen
83                .iter()
84                .map(|segment| segment.pma.len())
85                .sum::<usize>()
86    }
87
88    pub fn is_empty(&self) -> bool {
89        self.active.pma.is_empty() && self.frozen.is_empty()
90    }
91
92    /// Approximate bytes held — the spill-threshold signal.
93    pub fn approx_bytes(&self) -> u64 {
94        self.byte_size
95    }
96
97    /// Newest version of `row_id` with `epoch <= snapshot` (including
98    /// tombstones), mirroring `Memtable::get_version`. Returns `None` if no
99    /// version is visible. Seeks straight to `row_id`'s versions via the
100    /// PMA's gappy binary search instead of scanning from the front.
101    pub fn get_version(&self, row_id: RowId, snapshot_epoch: Epoch) -> Option<(Epoch, Row)> {
102        let mut best: Option<(Epoch, Row)> = None;
103        for pma in self
104            .frozen
105            .iter()
106            .map(|segment| &segment.pma)
107            .chain(std::iter::once(&self.active.pma))
108        {
109            for ((rid, epoch), row) in pma.iter_from(&(row_id, Epoch::ZERO)) {
110                if *rid != row_id {
111                    break;
112                }
113                if *epoch <= snapshot_epoch
114                    && best.as_ref().is_none_or(|(current, _)| *epoch > *current)
115                {
116                    best = Some((*epoch, row.clone()));
117                }
118            }
119        }
120        best
121    }
122
123    /// Newest visible version per `RowId` at `snapshot` (including tombstones),
124    /// ascending by `RowId` — mirroring `Memtable::visible_versions`.
125    pub fn visible_versions(&self, snapshot_epoch: Epoch) -> Vec<Row> {
126        let mut by_row: BTreeMap<RowId, Row> = BTreeMap::new();
127        for pma in self
128            .frozen
129            .iter()
130            .map(|segment| &segment.pma)
131            .chain(std::iter::once(&self.active.pma))
132        {
133            for ((rid, epoch), row) in pma.iter() {
134                if *epoch <= snapshot_epoch
135                    && by_row
136                        .get(rid)
137                        .is_none_or(|current| *epoch > current.committed_epoch)
138                {
139                    by_row.insert(*rid, row.clone());
140                }
141            }
142        }
143        by_row.into_values().collect()
144    }
145
146    pub(crate) fn seal(&mut self) {
147        if self.active.pma.is_empty() {
148            return;
149        }
150        let active = std::mem::replace(
151            &mut self.active,
152            MutableRunSegment {
153                pma: Pma::new(),
154                byte_size: 0,
155            },
156        );
157        Arc::make_mut(&mut self.frozen).push(Arc::new(active));
158        if self.frozen.len() >= crate::MAX_READ_GENERATION_LAYERS {
159            self.consolidate();
160        }
161    }
162
163    fn consolidate(&mut self) {
164        let mut rows = self
165            .frozen
166            .iter()
167            .flat_map(|segment| segment.pma.iter().map(|(_, row)| row.clone()))
168            .collect::<Vec<_>>();
169        rows.sort_by_key(|row| (row.row_id, row.committed_epoch));
170        let mut pma = Pma::new();
171        pma.extend_sorted(
172            rows.into_iter()
173                .map(|row| ((row.row_id, row.committed_epoch), row))
174                .collect(),
175        );
176        self.frozen = Arc::new(vec![Arc::new(MutableRunSegment {
177            pma,
178            byte_size: self.byte_size,
179        })]);
180    }
181
182    #[cfg(test)]
183    pub(crate) fn frozen_layer_count(&self) -> usize {
184        self.frozen.len()
185    }
186
187    /// Drain every version in ascending `(RowId, Epoch)` order — the order
188    /// `RunWriter::write` requires when spilling to an immutable run.
189    pub fn drain_sorted(&mut self) -> Vec<Row> {
190        let mut out = self
191            .frozen
192            .iter()
193            .flat_map(|segment| segment.pma.iter().map(|(_, row)| row.clone()))
194            .chain(self.active.pma.iter().map(|(_, row)| row.clone()))
195            .collect::<Vec<_>>();
196        out.sort_by_key(|row| (row.row_id, row.committed_epoch));
197        self.frozen = Arc::new(Vec::new());
198        self.active = MutableRunSegment {
199            pma: Pma::new(),
200            byte_size: 0,
201        };
202        self.byte_size = 0;
203        out
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use crate::memtable::Value;
211
212    fn row(id: u64, epoch: u64, v: i64) -> Row {
213        Row::new(RowId(id), Epoch(epoch)).with_column(1, Value::Int64(v))
214    }
215
216    fn tomb(id: u64, epoch: u64) -> Row {
217        Row {
218            row_id: RowId(id),
219            committed_epoch: Epoch(epoch),
220            columns: std::collections::HashMap::new(),
221            deleted: true,
222        }
223    }
224
225    fn int_of(r: &Row) -> i64 {
226        match r.columns.get(&1) {
227            Some(Value::Int64(x)) => *x,
228            _ => panic!("expected Int64 column"),
229        }
230    }
231
232    #[test]
233    fn get_version_returns_newest_visible() {
234        let mut mr = MutableRun::new();
235        mr.insert_many(vec![row(1, 1, 10), row(1, 3, 30), row(1, 9, 90)]);
236        // Snapshot before the 9 version sees the 3 version.
237        assert_eq!(int_of(&mr.get_version(RowId(1), Epoch(5)).unwrap().1), 30);
238        // Latest snapshot sees the newest.
239        assert_eq!(int_of(&mr.get_version(RowId(1), Epoch(9)).unwrap().1), 90);
240        // No version at/before epoch 0.
241        assert!(mr.get_version(RowId(1), Epoch(0)).is_none());
242        // Missing row.
243        assert!(mr.get_version(RowId(2), Epoch(100)).is_none());
244    }
245
246    #[test]
247    fn tombstone_is_returned_as_a_version() {
248        let mut mr = MutableRun::new();
249        mr.insert_many(vec![row(1, 1, 10), tomb(1, 2)]);
250        let v = mr.get_version(RowId(1), Epoch(5)).unwrap().1;
251        assert!(v.deleted);
252        // Before the tombstone the live version is visible.
253        let v0 = mr.get_version(RowId(1), Epoch(1)).unwrap().1;
254        assert!(!v0.deleted);
255    }
256
257    #[test]
258    fn sealed_generations_share_rows_and_consolidate() {
259        let mut writer = MutableRun::new();
260        for id in 0..crate::MAX_READ_GENERATION_LAYERS as u64 + 2 {
261            writer.insert_many(vec![row(id, id + 1, id as i64)]);
262            writer.seal();
263        }
264        assert!(writer.frozen_layer_count() < crate::MAX_READ_GENERATION_LAYERS);
265        let generation = writer.clone();
266        writer.insert_many(vec![row(99, 99, 99)]);
267        assert!(generation.get_version(RowId(99), Epoch(99)).is_none());
268        assert!(writer.get_version(RowId(99), Epoch(99)).is_some());
269    }
270
271    #[test]
272    fn visible_versions_dedups_to_newest_ascending() {
273        let mut mr = MutableRun::new();
274        mr.insert_many(vec![
275            row(3, 1, 30),
276            row(1, 1, 10),
277            row(2, 9, 20), // future relative to snapshot 5
278            row(1, 3, 11), // newer version of row 1
279            row(3, 2, 31),
280        ]);
281        let out = mr.visible_versions(Epoch(5));
282        let got: Vec<(u64, i64)> = out.iter().map(|r| (r.row_id.0, int_of(r))).collect();
283        assert_eq!(got, vec![(1, 11), (3, 31)], "row 2 hidden, newest wins");
284    }
285
286    #[test]
287    fn drain_sorted_is_ascending_version_order_and_empties() {
288        let mut mr = MutableRun::new();
289        mr.insert_many(vec![row(3, 1, 0), row(1, 2, 0), row(1, 1, 0), row(2, 1, 0)]);
290        let out = mr.drain_sorted();
291        let keys: Vec<(u64, u64)> = out
292            .iter()
293            .map(|r| (r.row_id.0, r.committed_epoch.0))
294            .collect();
295        assert_eq!(keys, vec![(1, 1), (1, 2), (2, 1), (3, 1)]);
296        assert!(mr.is_empty());
297        assert_eq!(mr.approx_bytes(), 0);
298    }
299
300    #[test]
301    fn many_inserts_stay_queryable() {
302        let mut mr = MutableRun::new();
303        let mut rows = Vec::new();
304        for i in 0..500u64 {
305            rows.push(row(i, 1, i as i64));
306        }
307        mr.insert_many(rows);
308        assert_eq!(mr.len(), 500);
309        for i in 0..500u64 {
310            assert_eq!(
311                int_of(&mr.get_version(RowId(i), Epoch(1)).unwrap().1),
312                i as i64
313            );
314        }
315        assert!(mr.approx_bytes() > 0);
316    }
317}