]> git.hungrycats.org Git - bees/commitdiff
roots: start each crawl window one transid past the last
authorZygo Blaxell <bees@furryterror.org>
Tue, 1 Sep 2026 04:36:18 +0000 (00:36 -0400)
committerZygo Blaxell <bees@furryterror.org>
Sat, 5 Sep 2026 04:04:14 +0000 (00:04 -0400)
Both ends of a crawl window are inclusive: find_next_extent rejects
extents below m_min_transid as crawl_gen_low and above m_max_transid as
crawl_gen_high.  Successive windows were built as [min, max] followed by
[max, next_max], so every extent whose generation landed exactly on a
window boundary was scanned twice -- once as one window's upper bound
and again as the next window's lower bound.  On the reference corpus
that is 519 restarts worth of boundary transids.

The overlap was load-bearing while the crawler could reach the open
transid: an extent created at gen == max_transid during a sweep, at a
bytenr the cursor had already passed, would be missed by that window,
and a disjoint next window would never look at that transid again.

It is not load-bearing now.  temp_transid_min() is floored at the
current transid, so effective_transid_max() is always strictly below
it and every window covers only committed transactions.  A committed
transaction's extent set cannot grow, so everything at the window's
upper bound already existed when the sweep started and the sweep will
reach it.  The open transid is the only one whose membership in the
scan set is in doubt, and it is now never scanned.

Start the next window at max + 1.  The comment records the dependency
so the overlap is restored if that floor ever goes away.

Assisted-by: Claude-Code:claude-opus-5
src/bees-roots.cc

index 05f3f7aeaaaa79b8168ec28a34e76988ea74819f..24d539b45a09424c08d00336991eab982717591e 100644 (file)
@@ -2944,8 +2944,25 @@ BeesCrawl::restart_crawl_unlocked()
                        m_deferred = true;
                        BEESLOGINFO("Crawl waiting for transid span: " << available << " available, " << span << " required");
                } else {
-                       // Start new crawl
-                       crawl_state.m_min_transid = crawl_state.m_max_transid;
+                       // Start new crawl.
+                       //
+                       // The new window begins one past the old one, not at it.
+                       // Both ends of the range are inclusive (see the gen_low /
+                       // gen_high filters in find_next_extent), so starting at the
+                       // old max would scan every extent at that transid a second
+                       // time -- once as the previous window's upper bound and
+                       // again as this window's lower bound.
+                       //
+                       // This is only safe because effective_transid_max() is held
+                       // strictly below the current transid by the temp extent
+                       // floor.  Every transid a window covers is therefore already
+                       // committed, and a committed transaction's extent set cannot
+                       // grow, so no extent can appear at the old max after the
+                       // sweep's cursor has passed it.  The open transid is the only
+                       // one whose membership in the scan set is still in doubt, and
+                       // it is never scanned.  If that floor is ever removed, this
+                       // must go back to overlapping by one.
+                       crawl_state.m_min_transid = crawl_state.m_max_transid + 1;
                        crawl_state.m_max_transid = target_max;
                        crawl_state.m_objectid = 0;
                        crawl_state.m_offset = 0;