fix: address P1 and P2 review issues from PR #8 round 7
indexer: clear duplicate scan cache (folder + global) at the end of every do_index so stale groups are never presented after a reindex; previously a file modified or replaced on disk would still appear as a duplicate candidate from the cached result until a manual rescan db/vector: replace timestamp-based HNSW revision with a monotonically incremented integer counter (app_kv.embedding_revision); mark_embedding_ready atomically increments the counter so two embeddings saved within the same second correctly advance the revision; get_embedding_revision now reads this counter instead of MAX(embedding_updated_at), preventing the HNSW cache from serving stale vectors after same-second batch updates
This commit is contained in:
@@ -263,6 +263,13 @@ pub fn migrate(conn: &Connection) -> Result<()> {
|
|||||||
groups_json TEXT NOT NULL
|
groups_json TEXT NOT NULL
|
||||||
);
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS app_kv (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value INTEGER NOT NULL DEFAULT 0
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT OR IGNORE INTO app_kv (key, value) VALUES ('embedding_revision', 0);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_images_folder_id ON images(folder_id);
|
CREATE INDEX IF NOT EXISTS idx_images_folder_id ON images(folder_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_images_modified_at ON images(modified_at);
|
CREATE INDEX IF NOT EXISTS idx_images_modified_at ON images(modified_at);
|
||||||
CREATE INDEX IF NOT EXISTS idx_embedding_jobs_status ON embedding_jobs(status);
|
CREATE INDEX IF NOT EXISTS idx_embedding_jobs_status ON embedding_jobs(status);
|
||||||
@@ -915,6 +922,13 @@ pub fn mark_embedding_ready(conn: &Connection, image_id: i64, model: &str) -> Re
|
|||||||
params![image_id, model],
|
params![image_id, model],
|
||||||
)?;
|
)?;
|
||||||
conn.execute("DELETE FROM embedding_jobs WHERE image_id = ?1", [image_id])?;
|
conn.execute("DELETE FROM embedding_jobs WHERE image_id = ?1", [image_id])?;
|
||||||
|
// Advance the monotonic revision so the HNSW cache is always rebuilt after
|
||||||
|
// any embedding change, regardless of clock resolution.
|
||||||
|
conn.execute(
|
||||||
|
"INSERT INTO app_kv (key, value) VALUES ('embedding_revision', 1)
|
||||||
|
ON CONFLICT(key) DO UPDATE SET value = value + 1",
|
||||||
|
[],
|
||||||
|
)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -373,6 +373,11 @@ fn do_index(app: AppHandle, pool: &DbPool, folder_id: i64, folder_path: PathBuf)
|
|||||||
let _ = db::backfill_embedding_jobs(&conn)?;
|
let _ = db::backfill_embedding_jobs(&conn)?;
|
||||||
db::update_folder_count(&conn, folder_id)?;
|
db::update_folder_count(&conn, folder_id)?;
|
||||||
let _ = db::clear_folder_scan_error(&conn, folder_id);
|
let _ = db::clear_folder_scan_error(&conn, folder_id);
|
||||||
|
// Invalidate duplicate scan cache — any reindex can change file contents
|
||||||
|
// or the set of files, making cached duplicate groups stale.
|
||||||
|
let folder_scope = format!("folder:{}", folder_id);
|
||||||
|
let _ = db::clear_duplicate_scan_cache(&conn, &folder_scope);
|
||||||
|
let _ = db::clear_duplicate_scan_cache(&conn, "all");
|
||||||
}
|
}
|
||||||
|
|
||||||
emit_progress(
|
emit_progress(
|
||||||
|
|||||||
+11
-7
@@ -215,13 +215,17 @@ pub fn get_image_embedding(conn: &Connection, image_id: i64) -> Result<Option<Ve
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_embedding_revision(conn: &Connection) -> Result<String> {
|
pub fn get_embedding_revision(conn: &Connection) -> Result<String> {
|
||||||
let count: i64 = conn.query_row("SELECT COUNT(*) FROM image_vec", [], |row| row.get(0))?;
|
// Use the monotonically incremented app_kv counter so that two embeddings
|
||||||
let max_updated_at: Option<String> = conn.query_row(
|
// saved within the same clock second still advance the revision, preventing
|
||||||
"SELECT MAX(embedding_updated_at) FROM images WHERE embedding_status = 'ready'",
|
// the HNSW cache from serving stale vectors.
|
||||||
[],
|
let revision: i64 = conn
|
||||||
|row| row.get(0),
|
.query_row(
|
||||||
)?;
|
"SELECT COALESCE((SELECT value FROM app_kv WHERE key = 'embedding_revision'), 0)",
|
||||||
Ok(format!("{}:{}", count, max_updated_at.unwrap_or_default()))
|
[],
|
||||||
|
|row| row.get(0),
|
||||||
|
)
|
||||||
|
.unwrap_or(0);
|
||||||
|
Ok(revision.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
// fn image_ids_for_folder(
|
// fn image_ids_for_folder(
|
||||||
|
|||||||
Reference in New Issue
Block a user