Skip to content

feat(mem_wal): flush active memtable on ShardWriter::close#6717

Merged
jackye1995 merged 1 commit into
lance-format:mainfrom
touch-of-grey:FlushMemtableOnShardWriterClose
May 13, 2026
Merged

feat(mem_wal): flush active memtable on ShardWriter::close#6717
jackye1995 merged 1 commit into
lance-format:mainfrom
touch-of-grey:FlushMemtableOnShardWriterClose

Conversation

@touch-of-grey

Copy link
Copy Markdown
Contributor

Summary

ShardWriter::close() previously drained the WAL but left the active memtable in memory at shutdown. Any rows that hadn't crossed max_memtable_size/is_batch_store_full triggers were durable in the WAL (recoverable on replay) but never produced a Lance fragment — so no LSM-level generation existed for them.

That's surprising semantics for callers who reasonably expect close() to make all data fully durable in the LSM sense, and it makes flush-cost benchmarks impossible: a workload that intentionally sets a huge max_memtable_size so puts measure pure steady-state throughput has no flush to time at close.

Change

Make close() freeze the active memtable (if non-empty) and await every frozen memtable's flush completion before shutting the task handlers down. WAL drain still runs first, so WAL ordering is unchanged.

Test

test_close_flushes_active_memtable drives 50 puts with max_memtable_size = usize::MAX, asserts no auto-flush fired during puts (precondition), then verifies close() completes without hanging. Without this change close returns promptly but no Lance generation exists; with it, close awaits the actual fragment write.

Full cargo test -p lance --lib mem_wal:: is 241 passed, 1 ignored.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@github-actions github-actions Bot added the enhancement New feature or request label May 8, 2026
touch-of-grey added a commit to touch-of-grey/lancedb that referenced this pull request May 8, 2026
Pin moves from lance-format/lance#6715 merge SHA to
lance-format/lance#6717's rev, so `close_merge_insert_writers()`
now flushes the active memtable into a Lance fragment instead of
dropping it. Required for sustained-throughput benchmarks that
intentionally raise `max_memtable_size` above the workload size to
measure pure put rate vs close-time drain separately.
@codecov

codecov Bot commented May 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@hamersaw hamersaw left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! This looks great, ease of life things.

Comment thread rust/lance/src/dataset/mem_wal/write.rs Outdated
Comment on lines +1764 to +1768
// Errors here are logged inside freeze_memtable and
// its message-send is fire-and-forget; we still
// try to drain anything already frozen.
let _ = writer_state.freeze_memtable(&mut st);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says that freeze_memtable logs errors internally, but I don't think that is accurate. So we are essentially dropping the error here without any visability. I think it makes sense to either (1) make sure we actually log at the "error" level or (2) capture and throw the error. API-wise I think it would make sense to have error thrown if I call shard_writer.close() and it is not successful.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in the latest push. freeze_memtable does not log internally, so the comment was wrong and silently dropping the error was wrong. Reworked the code to propagate the error: freeze_memtable(&mut st)? from inside close(). Since close() already returns Result<()>, the freeze failure now surfaces to the caller, as you suggested.

Comment thread rust/lance/src/dataset/mem_wal/write.rs Outdated
Comment on lines +2754 to +2756
// close() must flush the memtable into a Lance generation.
writer.close().await.unwrap();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this just validates that close completes. Should we a few steps further and validate that (1) close executions without errors (if we're throwing from close call), (2) the currently active memtable now has 0 rows, and (3) the L0 cached memtable (on-disk) exists?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beefed up the test. It now:

  1. asserts close() returns Ok explicitly (expect("close() must succeed and propagate any freeze/flush error")),
  2. reopens the same shard via a second ShardWriter::open,
  3. reads the persisted manifest and asserts manifest.current_generation > initial_gen — direct evidence that close() drove a MemTable flush + manifest commit, not just dropped the rows on the floor.

I considered also asserting the reopened active memtable's row_count == 0, but that wedges into WAL-replay behavior (replay still rehydrates entries because state.last_flushed_wal_entry_position isn't updated as WAL flushes happen — it's only initialized from the manifest at open). That looks like a separate replay-coordination issue and test_memtable_replay_* is the right home for it. Happy to file a follow-up if you want it tracked.

Previously, `ShardWriter::close()` only drained the WAL — the active
memtable was dropped along with the writer without producing a Lance
fragment. Data was durable in the WAL (recoverable on replay), but
no LSM-level generation was created. That's surprising for callers
who reasonably expect close() to make all data fully durable in the
LSM sense, and it makes flush-cost benchmarks impossible: a workload
that doesn't cross `max_memtable_size` during puts has nothing to
measure at close.

Change `close()` to freeze the active memtable (if non-empty) and
await every frozen memtable's flush completion before shutting down
the task handlers. WAL drain still runs first, so WAL ordering is
unchanged.

Adds a regression test
(`test_close_flushes_active_memtable`) that drives 50 puts with
`max_memtable_size = usize::MAX`, asserts no auto-flush happened
during puts, and verifies close completes without hanging — without
this change close would return promptly but no Lance generation
would exist; with it, close awaits the actual fragment write.
@touch-of-grey
touch-of-grey force-pushed the FlushMemtableOnShardWriterClose branch from f0eca19 to 3d9b429 Compare May 13, 2026 07:25
@touch-of-grey

touch-of-grey commented May 13, 2026

Copy link
Copy Markdown
Contributor Author

@hamersaw @jackye1995 updated the PR based on comments if you can take a look

@touch-of-grey

Copy link
Copy Markdown
Contributor Author

I will post another PR for all the hnsw improvements I am doing based on testing in the past few days

@jackye1995 jackye1995 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks for the fix!

@jackye1995
jackye1995 merged commit 6f14cb8 into lance-format:main May 13, 2026
28 checks passed
touch-of-grey added a commit to touch-of-grey/lancedb that referenced this pull request May 13, 2026
Switch from the close-flush rev pin to the tagged v7.0.0-beta.8
release. The release includes lance-format/lance#6717 (close flushes
the active memtable) and #6715 (flush-task-tree defensive fixes), both
required by the LSM merge_insert dispatch.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants