Summary
The periodic flush sweeper snapshots the LRU of resident rollout stores. A store that is written to and then evicted from the LRU before the next flush tick has an unsealed active memtable that nobody is left to flush.
Evidence
crates/lance-context-server/src/state.rs:490-502 — the sweeper snapshots self.rollout_stores (the LRU) and iterates only over what is resident:
let resident: Vec<_> = {
let cache = state.rollout_stores.lock().await;
cache.iter().map(|(n, s)| (n.clone(), s.clone())).collect()
};
RolloutStore::close (crates/lance-context-core/src/rollout_store.rs:642-660) is the mechanism that drains the writer, and state.rs:565 calls it — but it needs confirming that every LRU eviction path reaches that call, including eviction triggered implicitly by LruCache::put when the cache is at capacity.
Note also that close() uses Arc::try_unwrap and silently gives up if another handle is live (rollout_store.rs:653-658):
So even on a path that does call close(), a concurrent in-flight add holding a cloned Arc makes the drain a no-op.
Impact
Same failure mode as #184: rows are durable but potentially never become visible. Most likely to bite deployments with many rollout stores and a small rollout_store_cache_capacity, where eviction is routine rather than exceptional.
Proposed work
- Confirm whether implicit LRU eviction (capacity overflow inside
put) reaches close().
- If not, hook eviction so the store is flushed/closed before the handle is dropped.
- Consider flushing before eviction rather than relying on
close(), since close()'s Arc::try_unwrap can no-op under concurrency.
- Add a metric for evictions that skipped a drain, so this is observable rather than silent.
Related
Summary
The periodic flush sweeper snapshots the LRU of resident rollout stores. A store that is written to and then evicted from the LRU before the next flush tick has an unsealed active memtable that nobody is left to flush.
Evidence
crates/lance-context-server/src/state.rs:490-502— the sweeper snapshotsself.rollout_stores(the LRU) and iterates only over what is resident:RolloutStore::close(crates/lance-context-core/src/rollout_store.rs:642-660) is the mechanism that drains the writer, andstate.rs:565calls it — but it needs confirming that every LRU eviction path reaches that call, including eviction triggered implicitly byLruCache::putwhen the cache is at capacity.Note also that
close()usesArc::try_unwrapand silently gives up if another handle is live (rollout_store.rs:653-658):So even on a path that does call
close(), a concurrent in-flightaddholding a clonedArcmakes the drain a no-op.Impact
Same failure mode as #184: rows are durable but potentially never become visible. Most likely to bite deployments with many rollout stores and a small
rollout_store_cache_capacity, where eviction is routine rather than exceptional.Proposed work
put) reachesclose().close(), sinceclose()'sArc::try_unwrapcan no-op under concurrency.Related
rollout_flush_interval_secs=0may leave rows durably written but permanently invisible #184