Feature/agentic loop fixes #111
No files matched your search
@@ -156,6 +156,25 @@ pub fn detect_missing_files_for_library(
|
|||||||
(deleted, next_offset)
|
(deleted, next_offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Re-gather query-planner statistics (`sqlite_stat1`).
|
||||||
|
///
|
||||||
|
/// The migrations run `ANALYZE` once, but the stats never refresh after
|
||||||
|
/// that. A snapshot taken while `image_exif.content_hash` was all-NULL
|
||||||
|
/// told the planner every hash matches every row, so the correlated
|
||||||
|
/// `content_hash = ...` subqueries in [`refresh_back_refs`] scanned
|
||||||
|
/// the index instead of seeking it: ~20s per tick holding the write
|
||||||
|
/// lock, starving chat persistence past `busy_timeout`. With fresh
|
||||||
|
/// stats the same pass takes ~0.1s. `PRAGMA optimize` doesn't catch
|
||||||
|
/// this on SQLite 3.45 (verified), hence a plain `ANALYZE`. It costs
|
||||||
|
/// well under a second at current scale, so run it on full-scan ticks.
|
||||||
|
pub fn refresh_planner_stats(conn: &mut SqliteConnection) {
|
||||||
|
let started = std::time::Instant::now();
|
||||||
|
match sql_query("ANALYZE").execute(conn) {
|
||||||
|
Ok(_) => debug!("ANALYZE completed in {:?}", started.elapsed()),
|
||||||
|
Err(e) => warn!("ANALYZE failed: {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Refresh the `(library_id, rel_path)` back-refs on hash-keyed
|
/// Refresh the `(library_id, rel_path)` back-refs on hash-keyed
|
||||||
/// tables. A back-ref is stale when:
|
/// tables. A back-ref is stale when:
|
||||||
/// - its `content_hash` is non-null,
|
/// - its `content_hash` is non-null,
|
||||||
|
|||||||
+2
-2
@@ -27,7 +27,7 @@ where
|
|||||||
Err(e) => e,
|
Err(e) => e,
|
||||||
};
|
};
|
||||||
for attempt in 1..=max_retries {
|
for attempt in 1..=max_retries {
|
||||||
let base = Duration::from_millis(100).saturating_mul(1_u32.pow(attempt - 1));
|
let base = Duration::from_millis(100).saturating_mul(2_u32.pow(attempt - 1));
|
||||||
let jitter_range = base.as_millis() as f64 * 0.25;
|
let jitter_range = base.as_millis() as f64 * 0.25;
|
||||||
let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128;
|
let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128;
|
||||||
let delay = base.as_millis() as i128 + jitter as i128;
|
let delay = base.as_millis() as i128 + jitter as i128;
|
||||||
@@ -92,7 +92,7 @@ where
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
let base = Duration::from_millis(100).saturating_mul(1_u32.pow(attempt - 1));
|
let base = Duration::from_millis(100).saturating_mul(2_u32.pow(attempt - 1));
|
||||||
let jitter_range = base.as_millis() as f64 * 0.25;
|
let jitter_range = base.as_millis() as f64 * 0.25;
|
||||||
let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128;
|
let jitter = rand::thread_rng().gen_range(-jitter_range..=jitter_range) as u128;
|
||||||
let delay = base.as_millis() as i128 + jitter as i128;
|
let delay = base.as_millis() as i128 + jitter as i128;
|
||||||
|
|||||||
@@ -577,6 +577,9 @@ pub fn watch_files(
|
|||||||
// model" for the rules.
|
// model" for the rules.
|
||||||
{
|
{
|
||||||
let mut conn = image_api::database::connect();
|
let mut conn = image_api::database::connect();
|
||||||
|
if is_full_scan {
|
||||||
|
library_maintenance::refresh_planner_stats(&mut conn);
|
||||||
|
}
|
||||||
let _ = image_api::database::reconcile::run(&mut conn);
|
let _ = image_api::database::reconcile::run(&mut conn);
|
||||||
|
|
||||||
// Back-ref refresh: hash-keyed rows whose
|
// Back-ref refresh: hash-keyed rows whose
|
||||||
|
|||||||
Reference in new issue
Block a user