Repository navigation
feat(server): scale rollout stores via registry + LRU + global sweeper - #136
Merged
Merged
Conversation
…try + LRU + global sweeper Per-experiment physical partitioning (one dataset per experiment) requires the server runtime to decouple resident memory and background tasks from the number of stores. Three changes make that safe at 100k+ experiments: - Durable directory (`RolloutRegistry`, a single Lance table) is the source of truth for which stores exist. `create`/`list`/`delete`/existence checks go through it instead of the in-memory map, so eviction can no longer cause a false 404 and `list` enumerates every store, not just resident ones. - Bounded LRU replaces the unbounded `HashMap` of store handles; evicted stores are transparently reopened on the next request. - A single process-wide WAL-cleanup sweeper replaces the former one-tokio-timer-per-store model (which would spawn 100k+ timers). It sweeps the resident set with the same per-pass timeout as the periodic cleanup. `RolloutStoreInfo.version` becomes `Option<u64>` (omitted in list responses to avoid opening every dataset; populated for single-store get/create). Adds `--rollout-cache-capacity` / `ROLLOUT_CACHE_CAPACITY`. Co-Authored-By: Claude Opus 4 <noreply@anthropic.com>
4 tasks done
beinan
added a commit
that referenced
this pull request
Jul 14, 2026
…I) (#137) ## Summary First slice of the **`lance-context-master`** control-plane process for the per-experiment rollout deployment (builds on #136's registry + LRU). Delivers the read-only observability surface; CompactionScheduler and the React UI follow in separate PRs. - **core**: `RolloutStore::observe() -> RolloutObservation` + read-only `pending_wal_generations()` — cheap metadata-only metrics (row/fragment counts, manifest version + timestamp, pending MemWAL generations); no full scan. - **api**: `ExperimentSummary` / `ExperimentDetail` / `ExperimentListResponse` / `CompactJobStatus` DTOs. - **master**: new crate with - `StatsStore` (`_stats.rollout.lance`, delete-then-append idempotent upsert, `name LIKE` search + pagination), - bounded-concurrency background `StatsScanner` that enumerates the registry, observes each store read-only under a per-experiment timeout, and reconciles away deleted experiments, - `GET /api/v1/experiments`, `GET /api/v1/experiments/{name}` (`?fresh=true` recompute), `POST /api/v1/rescan`, - optional `--ui-dir` ServeDir wiring (SPA fallback) for the later UI PR. ## Design Master shares the data-plane's `data_dir`. It **reads** the registry (data-plane-owned) and **owns** the stats table — no write conflict. Pure direct connection: master opens datasets itself; it does not go through the data-plane. ## Test plan - [x] core: `observe_reports_cheap_metrics` - [x] master: stats_store idempotency / get+remove / LIKE search + pagination / reopen / sentinel mapping - [x] master: scan populates + list/search; scan reconciles removed experiments; detail 404 - [x] `cargo test` / `clippy` / `fmt` green for core/api/master 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4 <noreply@anthropic.com>
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.
Why
We want to partition rollouts by
experiment_name— one physical Lance dataset per experiment. At the target scale (tens to hundreds of thousands of experiments) the current server runtime cannot cope:rollout_storesis an unboundedHashMapof open handles → never evicted → OOM.spawn_periodic_cleanup) → 100k+ background timers wedge the process.list_rollout_storesonly enumerates the in-memory cache, so with eviction it would be incomplete, and there is no durable directory to enumerate.This PR decouples resident memory / background tasks from the number of stores.
What
RolloutRegistry, newcrates/lance-context-core/src/registry.rs): a single small Lance table (name,uri,created_at) that is the source of truth for which stores exist.create/list/delete/ existence checks now go through it. Existence is no longer inferred from cache membership, so eviction cannot cause a false 404, andlistreturns every store.HashMap; evicted stores are transparently reopened from storage on the next request (reusingopen_existing_with_optionsfrom fix(server): lazily open stores on cache miss (multi-replica 404) #135).AppState::spawn_global_sweeper) replaces one-timer-per-store. It sweeps only the resident set each tick, with the same per-passtokio::time::timeouthardening as fix(rollout): bound periodic WAL cleanup pass with a timeout #133. Cold (evicted) stores accrue no new generations and are swept again once re-opened.RolloutStoreInfo.version→Option<u64>(omitted inlistto avoid opening every dataset; populated for single get/create). Client updated (unwrap_or(0)).--rollout-cache-capacity/ROLLOUT_CACHE_CAPACITY(default 2000).Tests
registry.rs: upsert idempotency, contains/remove, list, survive-reopen.cargo test --workspace,cargo clippy --workspace --all-targets,cargo fmt --checkall clean.Follow-ups (out of scope)
data_dirto backfill registry rows if a crash left a dataset without its row).registry.nameif the narrow scan becomes hot.🤖 Generated with Claude Code