Atomically swap vector search engine index files on save#1460
Open
edwinyyyu wants to merge 1 commit into
Open
Conversation
SQLiteVectorStore persists each collection's index by calling the search engine's save(), which wrote directly to the final path. A crash mid-write left a truncated/corrupt file. Because index_saved=True makes the on-disk index a durable contract (missing/corrupt is a hard IndexLoadError, not a silent empty rebuild), an interrupted save could render a collection unrecoverable. Write the index to a sibling temp file and swap it into place with os.replace (atomic on POSIX and Windows on the same filesystem), so a reader sees either the old or new index, never a partial write; a failed save leaves the previous index intact. Leftover temp files are cleared on load so a crash does not leak them across restarts. Implemented in the engines (shared index_persistence helper) rather than in SQLiteVectorStore/SQLiteVectorStoreCollection, since the index save location and number of files written differ across engine implementations. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
SQLiteVectorStorepersists each collection's index by calling the search engine'ssave(), which wrote directly to the final path. A crash partway through that write left a truncated/corrupt file on disk.This is worse than a transient I/O error because of the durability contract: once
index_saved=True, the on-disk index is treated as authoritative — a missing or unloadable file raisesIndexLoadErrorrather than silently rebuilding an empty index. So an interrupted save could leave a collection permanently unrecoverable.Fix
Write the index to a sibling temp file and swap it into place with
os.replace(viaPath.replace), which is atomic on POSIX and Windows when source and destination share a filesystem (guaranteed here — the temp file is adjacent to the target). As a result:load()so a crash doesn't leak them across restarts.A best-effort
fsyncof the temp file before the swap guards against the data-vs-rename durability gap on power loss; the atomic swap remains the actual correctness guarantee.Where it lives
Implemented in the engines via a shared
index_persistencehelper (atomic_index_write,clear_stale_index_temp), wired into bothHnswlibVectorSearchEngineandUSearchVectorSearchEngine— not inSQLiteVectorStore/SQLiteVectorStoreCollection. The index save location and the number of files written differ across engine implementations, so the atomic-swap responsibility belongs with the engine that owns its file layout.Tests
test_index_persistence.py: swap-on-success, preserve-existing + cleanup on failure, no target created on failure, stale-temp clearing.load()clears a stale temp file and still loads correctly.All
vector_search_engineandtest_sqlite_vector_storesuites pass;ruffandtyclean.🤖 Generated with Claude Code