diff --git a/crates/commitlog/src/index/indexfile.rs b/crates/commitlog/src/index/indexfile.rs index 9fb853036b8..2d0702de274 100644 --- a/crates/commitlog/src/index/indexfile.rs +++ b/crates/commitlog/src/index/indexfile.rs @@ -5,7 +5,7 @@ use std::{ mem, }; -use log::debug; +use log::{debug, trace}; use memmap2::MmapMut; use spacetimedb_paths::server::OffsetIndexFile; @@ -190,19 +190,32 @@ impl + From> IndexFileMut { self.inner.flush_async() } - /// Truncates the index file starting from the entry with a key greater than or equal to the given key. + /// Truncates the index file starting from the entry with a key greater than + /// or equal to the given key. + /// + /// If successful, `key` will no longer be in the index. pub(crate) fn truncate(&mut self, key: Key) -> Result<(), IndexError> { let key = key.into(); - let (found_key, index) = self.find_index(Key::from(key))?; + let (found_key, index) = self + .find_index(Key::from(key)) + .map(|(found, index)| (found.into(), index))?; - // If returned key is smalled than asked key, truncate from next entry - self.num_entries = if found_key.into() == key { + // If returned key is smaller than asked key, truncate from next entry + self.num_entries = if found_key == key { index as usize } else { index as usize + 1 }; let start = self.num_entries * ENTRY_SIZE; + trace!( + "truncate key={} found={} index={} num-entries={} start={}", + key, + found_key, + index, + self.num_entries, + start + ); if start < self.inner.len() { self.inner[start..].fill(0); diff --git a/crates/commitlog/src/segment.rs b/crates/commitlog/src/segment.rs index 7d21e20e615..89a022bb18b 100644 --- a/crates/commitlog/src/segment.rs +++ b/crates/commitlog/src/segment.rs @@ -695,6 +695,7 @@ mod tests { use super::*; use crate::{payload::ArrayDecoder, repo, Options}; use itertools::Itertools; + use pretty_assertions::assert_matches; use proptest::prelude::*; use spacetimedb_paths::server::CommitLogDir; use tempfile::tempdir; @@ -924,16 +925,28 @@ mod tests { // Truncating to any offset in the written range or larger // retains that offset - 1, or the max offset written. - let truncate_to: TxOffset = rand::random_range(1..=32); - let retained_key = truncate_to.saturating_sub(1).min(10); - let retained_val = retained_key * 128; - let retained = (retained_key, retained_val); - - writer.ftruncate(truncate_to, rand::random()).unwrap(); - assert_eq!(writer.head.key_lookup(truncate_to).unwrap(), retained); - // Make sure this also holds after reopen. - drop(writer); - let index = TxOffsetIndex::open_index_file(&index_path).unwrap(); - assert_eq!(index.key_lookup(truncate_to).unwrap(), retained); + for truncate_to in (2..=10u64).rev() { + let retained_key = truncate_to.saturating_sub(1).min(10); + let retained_val = retained_key * 128; + let retained = (retained_key, retained_val); + + writer.ftruncate(truncate_to, rand::random()).unwrap(); + assert_matches!( + writer.head.key_lookup(truncate_to), + Ok(x) if x == retained, + "truncate to {truncate_to} should retain {retained:?}" + ); + // Make sure this also holds after reopen. + let index = TxOffsetIndex::open_index_file(&index_path).unwrap(); + assert_matches!( + index.key_lookup(truncate_to), + Ok(x) if x == retained, + "truncate to {truncate_to} should retain {retained:?} after reopen" + ); + } + + // Truncating to 1 leaves no entries in the index + writer.ftruncate(1, rand::random()).unwrap(); + assert_matches!(writer.head.key_lookup(1), Err(IndexError::KeyNotFound)); } }