fix(rollout): surface unflushed rows in observe(), which undercounts - #192
Merged
Conversation
beinan
force-pushed
the
fix/rollout-observe-unflushed
branch
from
July 25, 2026 07:29
a462ed6 to
aa71ddc
Compare
`RolloutObservation::row_count` sums the base table and flushed MemWAL generations. Rows sitting in an unsealed memtable are in neither, so since the seal moved off the append path the count is permanently short by roughly one flush interval's worth of writes. Before that change every append was sealed before returning and the count was exact. That undercount is inherent to asynchronous flush and not a bug on its own — the problem is that it was invisible, and it feeds the control-plane stats scanner, capacity decisions, and the row counts shown in the UI. Add `unflushed_rows`, read from the resident writer's memtable stats, so the gap is observable: `row_count + unflushed_rows` is every row this instance has durably accepted, and a persistently non-zero value means the flush sweeper is not keeping up. Best-effort and non-failing — no resident writer, a WAL-only writer, or a fenced writer all report 0, so an observability read never fails on writer state. Deliberately not plumbed into the master's stats table: it reads this process's in-memory writer and cannot see another instance's memtable, so aggregating it across instances would be misleading. Also documents that `add`'s return value now carries no information about the append at all — the base version does not advance, and since the seal moved it no longer signals visibility either. Changing the signature would break `AddRolloutsResponse.version`, so that is left for a version bump. Closes #187 Co-Authored-By: Claude <noreply@anthropic.com>
beinan
force-pushed
the
fix/rollout-observe-unflushed
branch
from
July 25, 2026 07:47
aa71ddc to
6a7b1e5
Compare
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.
Closes #187.
Problem
RolloutObservation::row_countsums the base table and flushed MemWAL generations. Rows in an unsealed memtable are in neither.Before the seal moved off the append path, every
addsealed before returning, so this was exact. Now, in steady state,row_countis permanently short by roughly one flush interval's worth of writes — and nothing anywhere exposed that.Consumers affected: the control-plane stats scanner, capacity and compaction decisions derived from it, and the row counts shown in the UI. A store can accept writes, report fewer rows than it holds, and give no indication why.
Change
Add
RolloutObservation::unflushed_rows, read from the resident writer'smemtable_stats().row_count + unflushed_rowsis every row this instance has durably accepted, and a persistently non-zero value is the signal that the flush sweeper is not keeping up (or is disabled).Best-effort and non-failing by design: no resident writer, a WAL-only writer with no memtable, or a fenced writer all report
0. An observability read should never fail because of writer state.Not plumbed into the master's stats table. The value reads this process's in-memory writer and cannot see another instance's memtable, so aggregating it cross-instance would be actively misleading. It stays a local signal;
pending_wal_generationsremains the cross-instance one.Documented
add's return value as carrying no information: the base version does not advance on a MemWAL append, so it is a constant unrelated to the rows just written, and since the seal moved it no longer signals visibility either. The doc now says explicitly not to use it as a write handle or to poll for visibility.I did not change the signature to
LanceResult<()>as the issue floated — it would break the publicAddRolloutsResponse.versionDTO and every caller. Worth doing, but as its own breaking change with a version bump rather than smuggled into an observability fix.Testing
observe_reports_unflushed_rows_excluded_from_row_countwalks the full lifecycle: no writer → 0; two rows added →row_count == 0whileunflushed_rows == 2; after flush →row_count == 2,unflushed_rows == 0. This pins the undercount as intentional-and-visible rather than accidental.cargo test --workspace→ all green (162 core, 49 server, rest as before). fmt + clippy clean.🤖 Generated with Claude Code