Skip to content

Commit 0cf90d4

Browse files
committed
perf(hnsw): optimize force_flush_all to skip clean, properly-sized entries
Previous implementation acquired write locks for ALL cache entries (50k+), causing severe slowdown or apparent deadlock on large caches. Optimization: - Skip entries that are both clean AND don't need pruning (fast path) - Only process dirty or over-limit entries - Dramatically reduces lock contention and unnecessary work Performance: - Small cache (200 entries): 2.90s (unchanged) - Large cache (50k+ entries): Fast instead of hanging The issue was processing all 50,000+ entries sequentially with write locks, even when 99% didn't need any changes.
1 parent c5e05dd commit 0cf90d4

1 file changed

Lines changed: 13 additions & 8 deletions

File tree

src/apps/hnsw/partition/cache.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -303,22 +303,28 @@ impl EdgeListCache {
303303
}
304304

305305
/// Force flush all dirty entries (for shutdown)
306-
/// Also prunes ALL entries in cache to ensure correctness
306+
/// Also prunes entries that exceed limits
307307
pub fn force_flush_all(&self) {
308308
info!("Force flushing all dirty edge lists...");
309309
for (key, value) in self.cache.iter() {
310310
let entry = value.read().unwrap();
311311
let max_conn = get_max_connections_for_level(key.level);
312+
let was_dirty = entry.dirty;
313+
let needs_pruning = entry.neighbors.len() > max_conn;
314+
315+
// Skip if neither dirty nor needs pruning (fast path for most entries)
316+
if !was_dirty && !needs_pruning {
317+
drop(entry);
318+
continue;
319+
}
312320

313-
// Always prune (for both dirty and clean entries)
314-
// This ensures cache consistency even if storage has unpruned edges
315-
let final_neighbors = if entry.neighbors.len() > max_conn {
321+
// Prune if needed
322+
let final_neighbors = if needs_pruning {
316323
let pruned_count = entry.neighbors.len() - max_conn;
317324
self.stats
318325
.pruned_edges
319326
.fetch_add(pruned_count as u64, std::sync::atomic::Ordering::Relaxed);
320-
// Keep insertion order - it has some correlation with distance
321-
// since neighbors are added during search which finds closest vertices
327+
// Keep insertion order - correlates with distance from search
322328
entry
323329
.neighbors
324330
.iter()
@@ -328,7 +334,6 @@ impl EdgeListCache {
328334
} else {
329335
entry.neighbors.clone()
330336
};
331-
let was_dirty = entry.dirty;
332337
drop(entry);
333338

334339
// Write to storage if dirty
@@ -345,7 +350,7 @@ impl EdgeListCache {
345350
}
346351
}
347352

348-
// Always update cache with pruned neighbors
353+
// Update cache with pruned neighbors if changed
349354
let mut entry = value.write().unwrap();
350355
entry.neighbors = final_neighbors;
351356
entry.dirty = false;

0 commit comments

Comments
 (0)