Skip to content

Commit af2bf7a

Browse files
committed
Raise JOURNAL_GAP_THRESHOLD 1M→10M
- It was doing full rescans far too often. This should lead to better UX. - We recently raised REPLAY_EVENT_COUNT_LIMIT but it was not enough
1 parent a5fd302 commit af2bf7a

3 files changed

Lines changed: 7 additions & 7 deletions

File tree

apps/desktop/src-tauri/src/indexing/CLAUDE.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ App startup
3535
|-- start_indexing(): create IndexManager, open SQLite, spawn writer thread
3636
|-- resume_or_scan():
3737
| |-- macOS: Has existing index + last_event_id?
38-
| | |-- Pre-check: event gap > 1M? -> emit index-rescan-notification (StaleIndex), full scan
38+
| | |-- Pre-check: event gap > 10M? -> emit index-rescan-notification (StaleIndex), full scan
3939
| | |-- Otherwise -> sinceWhen replay (FSEvents journal)
4040
| |-- Linux: Always full rescan (no event journal; existing DB used for instant enrichment)
4141
| |-- Incomplete previous scan (has data but no scan_completed_at)? -> notify + fresh scan
@@ -134,7 +134,7 @@ Key test files are alongside each module (test functions within `#[cfg(test)]` b
134134

135135
**Subtree aggregation uses scoped queries**: `scoped_get_children_stats_by_id` and `scoped_get_child_dir_ids` in `aggregator.rs` use recursive CTEs scoped to the target subtree, not full-table scans. This keeps subtree aggregation O(subtree_size) regardless of total DB size.
136136

137-
**Bounded buffers prevent OOM**: All buffers have capacity limits. FSEvents channel: 32K batches (bounded `try_send` in cmdr-fsevent-stream; overflow sets atomic flag, triggers rescan). Reconciler buffer: 500K events (overflow triggers full rescan). Writer channel: 20K messages (bounded `sync_channel`, backpressure). Replay `affected_paths`: 50K entries (overflow emits full refresh). Replay `pending_rescans`: 1K entries (overflow triggers full rescan). Replay event count: 1M events max (overflow falls back to full scan). Memory watchdog: warns at 8 GB, stops indexing at 16 GB. The index is a disposable cache, so dropping events and rescanning is always safe.
137+
**Bounded buffers prevent OOM**: All buffers have capacity limits. FSEvents channel: 32K batches (bounded `try_send` in cmdr-fsevent-stream; overflow sets atomic flag, triggers rescan). Reconciler buffer: 500K events (overflow triggers full rescan). Writer channel: 20K messages (bounded `sync_channel`, backpressure). Replay `affected_paths`: 50K entries (overflow emits full refresh). Replay `pending_rescans`: 1K entries (overflow triggers full rescan). Replay event count: 10M events max (overflow falls back to full scan). Memory watchdog: warns at 8 GB, stops indexing at 16 GB. The index is a disposable cache, so dropping events and rescanning is always safe.
138138

139139
**Disposable cache pattern**: The index DB is a cache, not a source of truth. Any corruption or error triggers delete+rebuild. No user-facing errors for DB issues.
140140

@@ -144,7 +144,7 @@ Key test files are alongside each module (test functions within `#[cfg(test)]` b
144144

145145
**APFS firmlinks**: Scan from `/` only, skip `/System/Volumes/Data`. Normalize all paths via firmlink prefix map so DB lookups work regardless of how the user navigated to a path.
146146

147-
**Rescan notification system (`RescanReason` enum)**: Every code path that falls back to a full rescan emits an `index-rescan-notification` event with a `RescanReason` variant and human-readable details. The frontend maps each reason to a user-friendly toast message. Eight reasons: `StaleIndex` (pre-check gap), `JournalGap` (in-loop gap), `ReplayOverflow` (>1M events), `TooManySubdirRescans` (>1K MustScanSubDirs), `WatcherStartFailed`, `ReconcilerBufferOverflow` (>500K buffered events during scan), `IncompletePreviousScan` (has data but no `scan_completed_at`), `WatcherChannelOverflow` (FSEvents channel full, events dropped). The pre-check in `resume_or_scan()` catches stale indexes before starting the FSEvents stream, preventing the cmdr-fsevent-stream channel (32K capacity, `try_send`) from being overwhelmed.
147+
**Rescan notification system (`RescanReason` enum)**: Every code path that falls back to a full rescan emits an `index-rescan-notification` event with a `RescanReason` variant and human-readable details. The frontend maps each reason to a user-friendly toast message. Eight reasons: `StaleIndex` (pre-check gap), `JournalGap` (in-loop gap), `ReplayOverflow` (>10M events), `TooManySubdirRescans` (>1K MustScanSubDirs), `WatcherStartFailed`, `ReconcilerBufferOverflow` (>500K buffered events during scan), `IncompletePreviousScan` (has data but no `scan_completed_at`), `WatcherChannelOverflow` (FSEvents channel full, events dropped). The pre-check in `resume_or_scan()` catches stale indexes before starting the FSEvents stream, preventing the cmdr-fsevent-stream channel (32K capacity, `try_send`) from being overwhelmed.
148148

149149
## Gotchas
150150

apps/desktop/src-tauri/src/indexing/event_loop.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ pub(super) const LIVE_FLUSH_INTERVAL_MS: u64 = 1000;
2929
/// Threshold for detecting a journal gap. If the first event ID received is
3030
/// more than this many IDs ahead of the stored `since_event_id`, we consider
3131
/// the journal unavailable and fall back to a full scan.
32-
pub(super) const JOURNAL_GAP_THRESHOLD: u64 = 1_000_000;
32+
pub(super) const JOURNAL_GAP_THRESHOLD: u64 = 10_000_000;
3333

3434
/// Capacity of the watcher→event loop channel. Provides backpressure to
3535
/// FSEvents/inotify when the event loop can't keep up, preventing unbounded

apps/desktop/src-tauri/src/indexing/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,9 @@ impl IndexManager {
278278
let last_event_id: u64 = last_event_id_str.parse().unwrap_or(0);
279279
if last_event_id > 0 {
280280
// Pre-check: compare stored event ID with current system event ID.
281-
// If the gap is too large, skip replay entirely — the cmdr-fsevent-stream
282-
// channel (1024 capacity, try_send) would silently drop most events,
283-
// and replaying millions of events is slower than a fresh scan anyway.
281+
// If the gap is too large, skip replay entirely — replaying tens of
282+
// millions of events is slower than a fresh scan. The watcher channel
283+
// (32K capacity) has overflow detection as a secondary safety net.
284284
let current_id = watcher::current_event_id();
285285
if current_id > 0 && current_id > last_event_id + JOURNAL_GAP_THRESHOLD {
286286
let gap = current_id - last_event_id;

0 commit comments

Comments
 (0)