Skip to content

Commit 16ffa7c

Browse files
fix: make text index reindex atomic (#252)
Co-authored-by: Andrew Briscoe <awb@presempathy.com>
1 parent 8ec3fa4 commit 16ffa7c

1 file changed

Lines changed: 38 additions & 10 deletions

File tree

src/proximity/text_index.rs

Lines changed: 38 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -539,16 +539,25 @@ impl<const N: usize, E: Embedder, S: NodeStorage<N>> TextIndex<N, E, S> {
539539
where
540540
I: IntoIterator<Item = (Vec<u8>, String)>,
541541
{
542-
// Drop every chunk in the index. Cheaper than per-doc deletion since
543-
// we're rebuilding from scratch anyway.
544-
let ids: Vec<Vec<u8>> = self.inner.entries_snapshot().keys().cloned().collect();
545-
for id in ids {
546-
self.inner.remove(&id);
547-
}
548-
// Re-insert each doc through the chunker.
542+
let mut rebuilt =
543+
ProximityIndex::new(self.inner.storage().clone(), self.inner.config().clone());
549544
for (id, text) in texts {
550-
self.insert(&id, &text)?;
545+
let chunks = self.chunker.split(&text);
546+
if chunks.is_empty() {
547+
continue;
548+
}
549+
for (idx, chunk_text) in chunks.iter().enumerate() {
550+
let vec = self.embedder.embed(chunk_text)?;
551+
if vec.len() != usize::from(self.embedder.dim()) {
552+
return Err(TextIndexError::Embed(EmbedError::DimensionMismatch {
553+
expected: self.embedder.dim(),
554+
got: vec.len(),
555+
}));
556+
}
557+
rebuilt.insert(make_chunk_id(&id, idx as u32), vec)?;
558+
}
551559
}
560+
self.inner = rebuilt;
552561
Ok(())
553562
}
554563

@@ -640,7 +649,7 @@ mod tests {
640649

641650
fn embed(&self, text: &str) -> Result<Vec<f32>, EmbedError> {
642651
if text.contains(self.needle) {
643-
return Err(EmbedError::Failure("forced insert failure".to_string()));
652+
return Err(EmbedError::Failure("forced embedding failure".to_string()));
644653
}
645654
self.inner.embed(text)
646655
}
@@ -675,7 +684,7 @@ mod tests {
675684
idx.insert(b"doc:1", "stable text").unwrap();
676685

677686
let err = idx.insert(b"doc:1", "please fail-insert").unwrap_err();
678-
assert!(err.to_string().contains("forced insert failure"));
687+
assert!(err.to_string().contains("forced embedding failure"));
679688

680689
let hits = idx.search("stable text", 1).unwrap();
681690
assert_eq!(hits[0].id, b"doc:1".to_vec());
@@ -801,6 +810,25 @@ mod tests {
801810
assert_eq!(hits[0].id, b"c".to_vec());
802811
}
803812

813+
#[test]
814+
fn failed_reindex_preserves_existing_documents() {
815+
let storage = InMemoryNodeStorage::<32>::new();
816+
let mut idx = TextIndex::new(
817+
storage,
818+
TextIndexConfig::new(FailsOnNeedleEmbedder::new(8, "fail-reindex")),
819+
);
820+
idx.insert(b"doc:1", "stable text").unwrap();
821+
822+
let err = idx
823+
.reindex_from_texts(vec![(b"doc:1".to_vec(), "please fail-reindex".to_string())])
824+
.unwrap_err();
825+
assert!(err.to_string().contains("forced embedding failure"));
826+
827+
let hits = idx.search("stable text", 1).unwrap();
828+
assert_eq!(hits[0].id, b"doc:1".to_vec());
829+
assert!(hits[0].score < 1e-4);
830+
}
831+
804832
#[test]
805833
fn search_returns_at_most_k() {
806834
let storage = InMemoryNodeStorage::<32>::new();

0 commit comments

Comments
 (0)