Skip to content

feat(master): leader-elected planner folds demand events into a cache - #361

Merged
beinan merged 1 commit into
lance-format:mainfrom
beinan:feat/planner-leader-demand-cache
Oct 8, 2026
Merged

beinan merged 1 commit into
lance-format:mainfrom
beinan:feat/planner-leader-demand-cache

Conversation

@beinan

@beinan beinan commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Part of #333 (P1.5). Spec §1.2/§3. The gate (#359) is merged.

What

Behind PLANNER_ENABLED (default off): one master wins P/leader under a lease and every PLANNER_RECONCILE_SECS folds all demand-events/ into a per-table P/demand/<hex> cache via TableDemand::fold. Rows for tables with no events are dropped. The cache is a pure function of stored events — never a source of truth.

Shadow only. Places nothing, removes nothing; every existing loop keeps running (review point 5).

Outputs: the cache; GET /api/v1/scheduler/demand[?target=] (leader record + per-table pending/bytes/age/shards + full record); metrics planner_is_leader, planner_tables, planner_pending_generations_total, planner_reconcile_seconds, planner_events_skipped_total, planner_reconcile_errors_total.

Leadership

Campaign txn on Version(P/leader)==0 writes the record and a bare P/leader-token under one lease. Every cache-write txn compares the token; every reconcile reads it first even with nothing to write, so a deposed leader notices on the next tick. Writes chunked under etcd's 128-op limit. Losers return promptly and re-campaign after 2 s.

Validation

  • Unit: fold_events groups by table and skips bad key / bad body / future version (counted); hex round-trip.
  • etcd: two masters campaign → exactly one leads (the test detects a dual leader; mutation that ignores the campaign result fails it with finished=[false, false]); cache == direct fold; HTTP view shows leader and 42 pending on a two-shard table; forged token → leadership lost; deleting a table's events drops its row next tick.
  • Clippy -D warnings, fmt, typos clean.

🤖 Generated with Claude Code

P1.5 (docs/design/scheduler-p1-data-model.md §1.2, §3). Behind
PLANNER_ENABLED (default off), one master wins P/leader under a lease
and, every PLANNER_RECONCILE_SECS, folds every demand-events/ record
into a per-table P/demand/<hex> cache using TableDemand::fold. Rows for
tables with no events are dropped. The cache is a pure function of the
stored events and is never a source of truth.

Shadow only: this places nothing and removes nothing; every existing
loop keeps running for every table. Outputs are the cache, the new
GET /api/v1/scheduler/demand[?target=] view (leader record, per-table
pending counts/bytes/age/shards, full record) and metrics
(planner_is_leader, planner_tables, planner_pending_generations_total,
planner_reconcile_seconds, planner_events_skipped_total,
planner_reconcile_errors_total).

Leadership: campaign txn on Version(P/leader)==0 writing the record and
a bare P/leader-token under one lease; every cache write txn compares
the token; every reconcile reads the token first even with nothing to
write, so a deposed leader learns it on the next tick. Writes are
chunked under etcd's 128-op limit. Loser campaigns return promptly and
re-campaign after 2 s.

Validation: unit tests for fold_events (groups by table, skips bad key,
bad body and future version) and hex round-trip. etcd test: two masters
campaign, exactly one leads (the test detects a dual leader; a mutation
that ignores the campaign result fails it); the cache equals a direct
fold of the events; the HTTP view reports the leader and 42 pending on
the two-shard table; a forged token is refused with "leadership lost";
deleting a table's events drops its cache row on the next tick. Clippy
-D warnings, fmt, typos clean.

Refs lance-format#333.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@beinan
beinan merged commit b4f3093 into lance-format:main Oct 8, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant