fix(mem_wal): run grace eviction on its own task, not the flush handler's ticker - #9022
Open
xuanyu-z wants to merge 1 commit into
Open
fix(mem_wal): run grace eviction on its own task, not the flush handler's ticker#9022xuanyu-z wants to merge 1 commit into
xuanyu-z wants to merge 1 commit into
Conversation
xuanyu-z
force-pushed
the
xuanyuzhan/mem-wal-grace-eviction-own-task
branch
from
September 7, 2026 03:15
fc6eb52 to
a0ee16f
Compare
xuanyu-z
force-pushed
the
xuanyuzhan/mem-wal-grace-eviction-own-task
branch
from
September 7, 2026 03:36
a0ee16f to
d8cbb20
Compare
xuanyu-z
force-pushed
the
xuanyuzhan/mem-wal-grace-eviction-own-task
branch
from
September 7, 2026 03:48
d8cbb20 to
dbe27c3
Compare
xuanyu-z
force-pushed
the
xuanyuzhan/mem-wal-grace-eviction-own-task
branch
from
September 7, 2026 03:52
dbe27c3 to
52292e2
Compare
…er's ticker A frozen memtable's bytes are reclaimed only by the grace sweep. It ran as a ticker on the memtable-flush handler, and `TaskDispatcher` biases a handler's mailbox ahead of its ticker — deliberately, because a tick that only duplicates work a message would have done anyway must never starve a freeze completion or `close()`'s final append. That reasoning does not hold for this tick. While `handle()` is awaiting, the `select!` is not evaluated at all, so the ticker's only opportunity is the instant after a flush returns — and at any write rate where sealing outpaces flushing, the mailbox is never empty at that instant. The sweep then never runs for as long as the load lasts, so frozen memtables accumulate without bound and resident memory grows until the process is killed. Eviction now runs through a new `TaskExecutor::add_periodic`, which spawns work on its own task with the same cancellation and shutdown semantics as `add_handler`. `sweep_expired_frozen` becomes a free function over the shared state and memory view so a task that does not own the handler can run it. `TriggerMemTableFlush::SweepExpired` goes with it: the ticker was its only sender, so the variant, its `Debug` arm and the handler arm had no way to be reached. Doc references to it now name `sweep_expired_frozen`. The test asserts both halves — that a saturated handler starves its own ticker, and that a periodic task keeps its cadence beside it — so the distinction holds. Without the second assertion the test passes on the old code: a local-store flush is fast enough that the mailbox drains and the ticker runs.
xuanyu-z
force-pushed
the
xuanyuzhan/mem-wal-grace-eviction-own-task
branch
from
September 7, 2026 03:54
52292e2 to
613e57e
Compare
Contributor
There was a problem hiding this comment.
✅ Gate recommendation: approve.
The dedicated periodic worker addresses sweeper starvation without weakening mailbox priority or the grace/backpressure contract. This revision safely removes the obsolete SweepExpired message path that no longer had a sender, leaving one eviction mechanism; scheduler cadence and real grace eviction remain verified.
jackye1995
approved these changes
Sep 7, 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.
A frozen memtable's bytes are reclaimed only by the grace sweep. It ran as a ticker on the memtable-flush handler, and
TaskDispatcherbiases a handler's mailbox ahead of its ticker — deliberately, because a tick that only duplicates work a message would have done anyway must never starve a freeze completion orclose()'s final append.That reasoning does not hold for this tick. While
handle()is awaiting, theselect!is not evaluated at all, so the ticker's only opportunity is the instant after a flush returns — and at any write rate where sealing outpaces flushing, the mailbox is never empty at that instant. The sweep then never runs for as long as the load lasts, so frozen memtables accumulate without bound and resident memory grows until the process is killed.The change
Eviction runs through a new
TaskExecutor::add_periodic, which spawns work on its own task with the same cancellation and shutdown semantics asadd_handler.sweep_expired_frozenbecomes a free function over the shared state and memory view, so a task that does not own the handler can run it.The periodic timer uses the same missed-tick behaviour as the dispatcher:
Burstwould replay every tick missed while the work ran, which for slow work leaves the timer permanently ready and starves the cancellation arm.TriggerMemTableFlush::SweepExpiredis removed. The ticker was its only sender, so the variant, itsDebugarm and the handler arm became unreachable; doc references to it now namesweep_expired_frozen. Note for reviewers: that variant is reachable from outside the crate, so strictly this drops a public enum variant. Nothing constructs it, but say the word and I will keep it with an explicit sender instead.Testing
test_saturated_handler_starves_its_ticker_but_not_a_periodic_taskasserts both halves — that a saturated handler starves its own ticker, and that a periodic task keeps its cadence beside it — so the distinction holds. Without the second assertion the test passes on the old code: a local-store flush is fast enough that the mailbox drains and the ticker runs. It runs understart_paused, so the cadence costs no wall time.cargo fmt --all --check,ci/check_proto_comments.py, andcargo test -p lance --lib dataset::mem_wal(664 passing) are clean, as iscargo docfor intra-doc links. The two findingscargo clippysurfaces are pre-existing, inlance-io, which this PR does not touch.