Skip to content

feat: publish merged watermarks in the maintenance release transaction - #350

Merged
beinan merged 1 commit into
lance-format:mainfrom
beinan:feat/merged-watermarks-in-release
Oct 8, 2026
Merged

beinan merged 1 commit into
lance-format:mainfrom
beinan:feat/merged-watermarks-in-release

Conversation

@beinan

@beinan beinan commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Part of #333 (P1.3). Spec §1.1/§2.2 (#343), on top of #344/#345.

What

A successful WAL catch-up writes merged_through per shard in the same etcd txn that releases ownership, so demand and ownership change together and a planner never sees a released table whose pending count still includes the generations just merged.

  • core: rollout_append::merged_watermarks(uri) — per-shard merged generation from the base table's MemWAL index.
  • merge: Coordinator::release_with_demand(claim, execution, &[ShardMerged]). demand_release_changes only advances merged_through, keeps the writer's sealed/epoch/bytes, creates a fresh executor-sourced record for an unknown shard, and emits no compare/op for a shard already at or past the watermark. release is unchanged (delegates with &[]).
  • master: run_as reads watermarks after a successful Catchup and releases with them; read failure logs and releases without demand. Other kinds unchanged. Metric master_demand_merged_watermarks_total.

Additive; no key family removed; old masters ignore demand-events/.

Validation

  • release_changes_only_advance_merged_and_keep_writer_fields (etcd): advance 15 keeps sealed 20 / epoch 3 / bytes; lower 12 emits nothing; unknown shard → sealed == merged; fold → expected pending.
  • resident_replicas_merge_from_queue_with_no_workers_or_jobs extended: after both masters merge, each table has exactly two executor-sourced events whose merged_through equals merged_watermarks(uri) and which fold to pending_generations == 0.
  • 7 demand tests, 9 master etcd tests (resident + maintenance_execution) green; clippy -D warnings, fmt, typos clean.

🤖 Generated with Claude Code

Third P1 step (docs/design/scheduler-p1-data-model.md §1.1, §2.2). A
successful WAL catch-up now writes `merged_through` for every shard it
merged into the same etcd transaction that releases table ownership, so
a planner never observes a released table whose demand still counts the
generations just merged.

- core: `rollout_append::merged_watermarks(uri)` reads the base table's
  MemWAL index for the per-shard merged generation.
- merge: `Coordinator::release_with_demand` extends `release` with
  compare/op pairs from `demand_release_changes`, which only advances a
  shard's `merged_through`, keeps the writer's sealed/epoch/bytes fields,
  creates a fresh executor-sourced record for an unknown shard, and emits
  nothing for a shard already at or past the new watermark. `release`
  itself is unchanged and delegates with an empty list.
- master: `run_as` reads watermarks after a successful Catchup outcome
  and releases with them. A watermark read failure logs and releases
  without demand rather than stranding a completed merge. Other
  maintenance kinds release as before. Metric
  `master_demand_merged_watermarks_total`.

Validation: merge-crate etcd test for advance/keep/skip/fresh-shard and
a fold to the expected pending count; the resident two-master merge
test now asserts two executor-sourced events per table whose merged
watermarks equal the base table's and fold to zero pending. 7 demand
tests and 9 master etcd tests pass; clippy -D warnings, fmt, typos clean.

Refs lance-format#333.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@beinan
beinan merged commit 41e5719 into lance-format:main Oct 8, 2026
11 checks passed
beinan added a commit that referenced this pull request Oct 8, 2026
…ut (#357)

Part of #333. Fixes review finding **2 [P1]** (round 4) on #350 (credit:
production review). Independent of #352.

## Problems in #350

1. Watermark read after the watchdog exited and before `release`, with
**no deadline** → a stalled storage read holds a completed merge's
ownership.
2. Release txn compared every shard's demand key by `mod_revision` → **a
normal writer flush on any shard fails the whole release**. Reproduced.
3. 129+ shards → etcd default `--max-txn-ops=128` exceeded.

## Fix

- `release` is the plain ownership txn again; `release_with_demand` /
`demand_release_changes` removed.
- After a successful `Catchup` release:
`demand_publish::spawn_merged_demand` — detached, 15 s timeout, 4 slots,
drop-and-count, **one monotonic put per shard**, bounded CAS retry on a
lost race. Loss/lateness cannot misstate demand (per-dimension join;
scan reconciles).
- Both publish paths share `join_events` + retry, so the writer path no
longer silently returns `Ok(false)` after losing a race either.
- `master_demand_merged_watermarks_total{result}`.

## Validation

- New etcd test: writer `sealed=25` and executor `merged=18` published
**concurrently** on one shard → both succeed, stored `(25, Some(18))`;
lower merged no-op; unknown shard → fresh epoch-0 record.
- Resident two-master merge test polls for the detached publish; both
shards fold to zero pending.
- 7 demand, 9 maintenance/resident, 9 sweeper tests green against etcd;
clippy `-D warnings`, fmt, typos clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
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