From 41e01610675328e69d77bbf84074240e0584bef6 Mon Sep 17 00:00:00 2001 From: XYZhan Date: Mon, 10 Aug 2026 09:09:39 -0400 Subject: [PATCH] feat(lsm): move catch-up activation to an explicit API --- rust/lancedb/src/table.rs | 25 ++++++++++++++++++++++ rust/lancedb/src/table/merge/lsm.rs | 33 +++++++++++++++++++++++++---- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/rust/lancedb/src/table.rs b/rust/lancedb/src/table.rs index 1c9d68f4c..d3e8daa92 100644 --- a/rust/lancedb/src/table.rs +++ b/rust/lancedb/src/table.rs @@ -662,6 +662,15 @@ pub trait BaseTable: std::fmt::Display + std::fmt::Debug + Send + Sync { message: "set_lsm_write_spec is not supported on this table type".into(), }) } + /// Switch this table to required index catch-up, one way. + /// + /// The default implementation returns `NotSupported`. Implementations + /// that support the MemWAL LSM write path must override this. + async fn require_mem_wal_index_catchup(&self) -> Result<()> { + Err(Error::NotSupported { + message: "require_mem_wal_index_catchup is not supported on this table type".into(), + }) + } /// Remove the [`LsmWriteSpec`] from this table. /// /// This is a no-op if no spec is currently set. @@ -1697,6 +1706,18 @@ impl Table { self.inner.set_lsm_write_spec(spec).await } + /// Switch this table to required index catch-up, one way. + /// + /// Separate from [`Self::set_lsm_write_spec`] on purpose: a table carrying + /// the bit retains its SSTables until an index records that it holds the + /// compacted rows, so turn it on only once something can repair coverage. + /// + /// Errors if no spec is set, or if the table already records SSTable + /// compaction progress from before this protocol. + pub async fn require_mem_wal_index_catchup(&self) -> Result<()> { + self.inner.require_mem_wal_index_catchup().await + } + /// Remove the [`LsmWriteSpec`] from this table, reverting to the standard /// `merge_insert` write path. /// @@ -3147,6 +3168,10 @@ impl BaseTable for NativeTable { merge::lsm::set_lsm_write_spec(self, spec).await } + async fn require_mem_wal_index_catchup(&self) -> Result<()> { + merge::lsm::require_mem_wal_index_catchup(self).await + } + async fn unset_lsm_write_spec(&self) -> Result<()> { merge::lsm::unset_lsm_write_spec(self).await } diff --git a/rust/lancedb/src/table/merge/lsm.rs b/rust/lancedb/src/table/merge/lsm.rs index 525a637d8..ded6d1d10 100644 --- a/rust/lancedb/src/table/merge/lsm.rs +++ b/rust/lancedb/src/table/merge/lsm.rs @@ -114,10 +114,35 @@ pub(crate) async fn set_lsm_write_spec(table: &NativeTable, spec: LsmWriteSpec) builder = builder.add_writer_config_default(key, value); } builder.execute().await?; - // Require recorded index catch-up from the moment WAL is turned on, while - // the table is provably clean: no SSTable has been compacted, so there is - // no unvalidated progress to inherit, and the migration never has to be run - // against a table already in flight. + table.dataset.update(dataset); + Ok(()) +} + +// ============================================================================= +// require_mem_wal_index_catchup +// ============================================================================= + +/// Switch this table to required index catch-up, one way. +/// +/// Deliberately **not** part of installing the write spec. Until something can +/// actually repair coverage, a table carrying the bit reports every index as +/// not known to hold the compacted rows, so its SSTables are retained +/// indefinitely -- and the WAL pod trims on the legacy rule meanwhile, leaving +/// readers pointed at files that are gone. Turn this on only once remote +/// maintenance owns the merge and the repair for the table. +/// +/// Lance refuses the activation if the table already records SSTable +/// compaction progress: those numbers predate this protocol and cannot be +/// validated, so such a table must be drained rather than activated. +#[allow(clippy::redundant_pub_crate)] +pub(crate) async fn require_mem_wal_index_catchup(table: &NativeTable) -> Result<()> { + table.dataset.ensure_mutable()?; + let mut dataset = (*table.dataset.get().await?).clone(); + if dataset.mem_wal_index_details().await?.is_none() { + return Err(Error::InvalidInput { + message: "require_mem_wal_index_catchup: no LSM write spec is set on this table".into(), + }); + } dataset.require_mem_wal_index_catchup().await?; table.dataset.update(dataset); Ok(())