fix(cascade): record which beat lost the optimize commit race - #390
Merged
Conversation
The benign-conflict log said a commit race was lost but not by which
maintenance beat, and lance labels both beats' commit identically
("This Rewrite transaction was preempted by concurrent transaction ..."),
so the message alone cannot separate them. The costs differ sharply:
- a lost LIGHT beat is free — compaction retries ~10s later;
- a lost HEAVY beat means that table skipped a whole prune cadence, so
its superseded files stay on disk until the next one lands.
Reading an index-dir growth incident off the logs therefore meant
back-inferring which beats were heavy from the 300s cadence. That was
done once during the storage soak to explain a 13-minute window where
one table's cleanup stalled with no failure logged, and it cost two
35-minute debug-level reruns to confirm — the field makes it a grep.
Adds `pruned` to the conflict log, mirroring the sibling failure log.
Log level (debug) and the benign-conflict semantics are unchanged: the
streak is still not incremented and no fallback rebuild is triggered.
Test asserts both beats in one run: the heavy beat's conflict logs
pruned=True and the light beat's logs pruned=False. Mutation-verified —
dropping the field fails the test with KeyError.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
cyfyifanchen
approved these changes
Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds one field —
pruned— to the benign commit-conflict log:No behaviour change. Log level stays
debug, the failure streak is still notincremented, and no fallback rebuild is triggered.
Why
Lance labels both maintenance beats' commit identically:
Light beat (
optimize()) and heavy beat (optimize(cleanup_older_than=…)) areboth Rewrite transactions, so the message cannot tell them apart. The costs do
not match:
So a run of heavy-beat conflicts is exactly what precedes an index dir growing,
and today it is indistinguishable in the logs from harmless light-beat noise.
This is not hypothetical. During the storage soak one table's cleanup went
quiet for 13 minutes with zero failures logged (
optimize_failed,write_lock_deadline_exceeded,maintenance_task_timeoutall 0) whileprune_stale_secondsclimbed linearly. Establishing that those were lost heavybeats meant back-inferring beat types from the 300s cadence, then two 35-minute
debug-level reruns (~870MB of logs) to confirm. With
prunedit is a grep.The sibling failure log already carries
pruned=should_prune; the conflictbranch in the same
exceptsimply missed it, and the variable is already inscope.
Test
test_conflict_log_names_the_lost_beatdrives both beats in one run (frozenclock, so beat selection does not depend on runner uptime) and asserts the
heavy beat's conflict logs
pruned=Trueand the light beat'spruned=False.Mutation-verified: removing the field fails the test with
KeyError: 'pruned'.tests/unit/test_memory/test_cascade/— 202 passed, 0 skipped.make lintclean.🤖 Generated with Claude Code