[MaterializedView] Harden partition-state engine#18642
Conversation
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #18642 +/- ##
============================================
+ Coverage 56.79% 64.58% +7.79%
- Complexity 7 1288 +1281
============================================
Files 2579 3373 +794
Lines 149557 208651 +59094
Branches 24165 32583 +8418
============================================
+ Hits 84939 134761 +49822
- Misses 57424 63060 +5636
- Partials 7194 10830 +3636
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
xiangfu0
left a comment
There was a problem hiding this comment.
Found one high-signal correctness issue; see inline comment.
| // see `MaterializedViewPartitionManager#clearValid` for the design rationale (no | ||
| // fingerprint validation needed; the source contained zero overlapping segments by | ||
| // construction at scheduler dispatch time). | ||
| partitionManager.clearValid(tableName, windowStartMs); |
There was a problem hiding this comment.
This removes the commit-time source check from the DELETE path. If a backfill lands after the scheduler emitted DELETE but before postProcess() runs, the create-segment event is ignored because the partition is already STALE; then clearValid() flips it to VALID + EMPTY and the broker will rewrite queries to an empty MV bucket even though source rows now exist. The old behavior of removing the entry still fell back to base-table routing, so this is a silent wrong-results regression. DELETE needs a commit-time fingerprint/emptiness validation (or another way to preserve STALE on mid-flight backfills) before writing VALID + EMPTY.
…partition-state centralization This change consolidates two materialized-view bug fixes and two refactors that together close the gap between the broker's freshness contract and the actual MV ingestion state, and remove duplicated CAS / fingerprint logic that had drifted across the executor, scheduler, and consistency manager. 1. bugfix: Prevent watermarkMs from advancing past latest source data The MV scheduler previously kept advancing watermarkMs toward `now - bufferMs` even when the base table had stopped receiving new data, which caused the rewrite engine to treat stale MV data as fresh. watermarkMs is now capped at the latest endTime present in the source segments, so the staleness check `now - watermarkMs <= stalenessThresholdMs` reflects real data freshness instead of wall-clock drift. 2. bugfix: Persist VALID-empty on DELETE so backfilled buckets re-sync When a STALE bucket's source data was retention-deleted, the executor's DELETE branch used to remove the runtime PartitionInfo entry entirely. That left the bucket in the `absent` state, which the consistency manager's STALE-marking pass deliberately skips, so any subsequent base-table backfill into that window would never propagate to the MV. Fix: the DELETE branch now writes `VALID + PartitionFingerprint.EMPTY` instead of removing the entry. `absent` is now reserved for cold-start only; once any task has run for a bucket, an entry exists. A backfill into a previously-empty bucket flows through the standard `VALID -> STALE -> OVERWRITE` cycle the consistency manager already drives. PartitionFingerprint.EMPTY is byte-identical to what the scheduler's and executor's `computeWindowFingerprint` already produce when the overlapping segment list is empty (both feed an empty input through farmHashFingerprint64). This keeps empty-by-DELETE and empty-by-APPEND on a single representation, so existing ZK records remain comparable across rolling upgrades. 3. refactor: Centralize computeWindowFingerprint in MaterializedViewTaskUtils The scheduler and the minion executor each carried a verbatim copy of the filter+sort+farmHash64 algorithm used to fingerprint the source segments overlapping a partition window. Any drift between the two copies (different hasher, encoding, or sort key) would silently break the executor's commit-time fingerprint validation, producing one of the hardest classes of MV bugs to diagnose: tasks that fail validation on the way in, retry forever, and never advance the watermark. The algorithm now lives in MaterializedViewTaskUtils#computeWindowFingerprint as the single source of truth. Both the scheduler call sites and the executor call site invoke the utility directly with `_context.getSegmentsZKMetadata` inlined at the call site - no per-module wrapper survives, so there is nowhere for a future contributor to silently re-implement it. 4. refactor: Centralize all partition-state mutations through MaterializedViewPartitionManager Adds a state-change DSL backed by a single CAS engine that consolidates every per-partition mutation on the materialized-view runtime znode. The public methods (appendValid / refreshValid / clearValid / revertValid / markStale / deletePartition) map one-to-one to the per-partition state machine; private CRUD primitives enforce structural invariants on the in-memory partition map; applyMutation centralizes version-checked CAS retries with two retry profiles (critical, cluster-tunable; revert, fixed budget). The executor (APPEND -> appendValid, OVERWRITE -> refreshValid, DELETE -> clearValid), scheduler (false-positive STALE revert -> revertValid), and consistency manager (segment-change flush -> markStale) are switched onto the manager. Watermark advancement on APPEND now happens inside the manager's mutator under the same atomic write that adds the bucket entry, so map state and watermark cannot diverge under concurrent writers. The PartitionInfo constructor is locked to package access to encode the contract at compile time: production code outside the metadata package physically cannot synthesize a PartitionInfo without going through the manager. Cross-package tests use a single named factory `PartitionInfo.forTesting(...)` annotated `@VisibleForTesting`. Three bespoke CAS retry loops (DEFAULT_MAX_RUNTIME_UPDATE_ATTEMPTS in the executor, MAX_PARTITION_STATE_PERSIST_RETRIES in the scheduler, MAX_MARK_RETRIES + jittered backoff in the consistency manager) and their associated ThreadLocalRandom imports are removed; retry budget is now governed by a single CLUSTER_CONFIG_KEY_MAX_RUNTIME_UPDATE_ATTEMPTS cap. Signed-off-by: Hongkun Xu <xuhongkun666@163.com> Co-authored-by: Xiang Fu <xiangfu.1024@gmail.com>
af88fd6 to
fed52e5
Compare
There was a problem hiding this comment.
Pull request overview
This PR hardens Apache Pinot’s Materialized View (MV) partition-state engine by centralizing runtime znode mutations into a single CAS-based manager, fixing correctness issues around retention-delete/backfill tracking and watermark advancement, and deduplicating window-fingerprint computation across scheduler/executor paths.
Changes:
- Introduces
MaterializedViewPartitionManageras the single owner for MV runtime partition-map + watermark mutations (CAS retries/backoff consolidated). - Changes DELETE handling to keep partitions “tracked but empty” (
VALID + PartitionFingerprint.EMPTY) instead of removing the entry, enabling later backfill to follow the normal refresh cycle. - Caps scheduler APPEND cutoff by the latest source-segment
endTimeMsand unifies window fingerprint computation viaMaterializedViewTaskUtils#computeWindowFingerprint.
Reviewed changes
Copilot reviewed 14 out of 14 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/main/java/org/apache/pinot/plugin/minion/tasks/materializedview/MaterializedViewTaskExecutor.java | Switches post-commit runtime updates to MaterializedViewPartitionManager and adjusts DELETE semantics to persist VALID-empty. |
| pinot-plugins/pinot-minion-tasks/pinot-minion-builtin-tasks/src/test/java/org/apache/pinot/plugin/minion/tasks/materializedview/MaterializedViewTaskExecutorTest.java | Rebases fingerprint/contiguous-watermark tests onto MaterializedViewTaskUtils and adds coverage for empty-overlap fingerprint equality with EMPTY. |
| pinot-materialized-view/src/main/java/org/apache/pinot/materializedview/metadata/MaterializedViewPartitionManager.java | New central CAS mutation engine + state-change DSL for MV runtime partition state transitions and watermark updates. |
| pinot-materialized-view/src/test/java/org/apache/pinot/materializedview/metadata/MaterializedViewPartitionManagerTest.java | New unit-test surface covering manager operations, preconditions, and CAS retry behavior. |
| pinot-materialized-view/src/main/java/org/apache/pinot/materializedview/scheduler/MaterializedViewTaskScheduler.java | Caps APPEND scheduling cutoff by max source segment end time; routes false-positive STALE reverts through the manager; uses shared fingerprint helper. |
| pinot-materialized-view/src/test/java/org/apache/pinot/materializedview/scheduler/MaterializedViewTaskSchedulerTest.java | Adds tests for computeMaxSourceEndTimeMs behavior, including legacy/unset end times. |
| pinot-materialized-view/src/main/java/org/apache/pinot/materializedview/scheduler/MaterializedViewTaskUtils.java | Adds shared computeWindowFingerprint implementation used by both scheduler and executor. |
| pinot-materialized-view/src/main/java/org/apache/pinot/materializedview/consistency/MaterializedViewConsistencyManager.java | Replaces bespoke CAS retry logic with manager-based markStale; refactors bucket enumeration + watermark cap behavior. |
| pinot-materialized-view/src/test/java/org/apache/pinot/materializedview/consistency/MaterializedViewConsistencyManagerTest.java | Updates fixtures to use PartitionInfo.forTesting(...). |
| pinot-materialized-view/src/main/java/org/apache/pinot/materializedview/metadata/PartitionFingerprint.java | Introduces PartitionFingerprint.EMPTY with farmHash64(empty) to canonicalize “empty window” fingerprints across paths. |
| pinot-materialized-view/src/test/java/org/apache/pinot/materializedview/metadata/PartitionFingerprintTest.java | Adds tests pinning EMPTY’s invariants (segmentCount=0, CRC matches farmHash64(empty), encode/decode). |
| pinot-materialized-view/src/main/java/org/apache/pinot/materializedview/metadata/PartitionInfo.java | Makes constructor package-private and adds PartitionInfo.forTesting(...) to discourage cross-package construction. |
| pinot-materialized-view/src/main/java/org/apache/pinot/materializedview/metadata/PartitionState.java | Updates state-model documentation to reflect tracked-empty semantics and reserved meaning of “absent”. |
| pinot-materialized-view/src/test/java/org/apache/pinot/materializedview/rewrite/MaterializedViewQueryRewriteEngineTest.java | Updates test fixture construction to use PartitionInfo.forTesting(...). |
| // DELETE mode: skip query execution; remove existing MV segments and rewrite the | ||
| // runtime PartitionInfo to VALID-empty (see updateMaterializedViewRuntime DELETE branch). | ||
| if (MaterializedViewTask.TASK_MODE_DELETE.equals(taskMode)) { |
| /// via segment lineage replace (segmentsFrom=[old segments], segmentsTo=[]). No query | ||
| /// is executed and no new MV segments are created; the runtime PartitionInfo is rewritten | ||
| /// to `VALID + PartitionFingerprint.EMPTY` by [#updateMaterializedViewRuntime] in | ||
| /// [#postProcess], so the partition stays tracked-but-empty rather than disappearing. |
| overlapping.sort(Comparator.comparing(SegmentZKMetadata::getSegmentName)); | ||
| Hasher hasher = Hashing.farmHashFingerprint64().newHasher(); | ||
| for (SegmentZKMetadata seg : overlapping) { | ||
| hasher.putString(seg.getSegmentName(), StandardCharsets.UTF_8); | ||
| hasher.putByte((byte) 0); | ||
| hasher.putLong(seg.getCrc()); | ||
| hasher.putByte((byte) '\n'); | ||
| } | ||
| return new PartitionFingerprint(overlapping.size(), hasher.hash().asLong()); |
| List<Long> buckets = new ArrayList<>(); | ||
| long partStart = Math.floorDiv(affectedStartMs, bucketMs) * bucketMs; | ||
| while (partStart <= affectedEndMs) { | ||
| PartitionInfo info = updatedInfos.get(partStart); | ||
| if (info != null && info.getState() == PartitionState.VALID) { | ||
| updatedInfos.put(partStart, info.withState(PartitionState.STALE)); | ||
| anyChanged = true; | ||
| markedCount++; | ||
| } | ||
| while (partStart <= cappedEnd) { | ||
| buckets.add(partStart); | ||
| partStart += bucketMs; | ||
| } |
| /// Lenient: if the bucket is absent or already STALE, the call is a no-op. Today the | ||
| /// in-coverage VACANT case is a deliberate skip (matching the prior consistency manager | ||
| /// behavior); a future change will synthesize a STALE entry for in-coverage VACANT | ||
| /// buckets to fix the backfill silent-data-loss case. | ||
| /// |
Review: PR #18642 — [MaterializedView] Harden partition-state engineRepo: apache/pinot Repo context loaded:
Stage 1: Triage & Evidence CollectionPR description
Findings:
Change summary
Summary: Files:
Risk classification
Classification: Medium Reason: The PR touches concurrency-sensitive ZK CAS code paths shared by three subsystems (executor, scheduler, consistency manager) and changes a public API ( Test gaps
Gaps:
Stage 2: Deep ReviewDoes the implementation address the stated problem?
Findings:
High-risk files (medium)
Findings:
Center of gravity (medium)
Files:
Why these are central: Invariants — targeted (medium)
Findings:
Test adequacy (medium)
Findings:
Collateral damage (medium)
Findings:
Path-triggered review (medium)For each touched path, the inferred failure mode and what was checked.
Touched paths and inferred risks:
VerdictRisk: medium Confidence reasoningThe PR is well-scoped, internally consistent, and addresses the two stated correctness bugs with documented byte-equality guarantees for rolling upgrades. The new manager has comprehensive unit-test coverage (24 tests across happy/strict/lenient/CAS-retry/budget-exhaust paths) and the centralization of three retry loops into one is a genuine maintainability win. CI is green across all check categories including binary compatibility and integration tests. Confidence is medium (not high) because:
These are concerns, not blockers — none would justify holding the merge given the centralization win and the self-healing fallback behavior. The PR meaningfully reduces future surface area for state-machine drift and removes a class of real correctness bugs. BlockersNone. Concerns
Observations
Areas recommended for human focus
Status
|
…ackfills Addresses the PR review: the DELETE path wrote VALID+EMPTY unconditionally. If a base-table backfill landed between the scheduler dispatching DELETE (on an empty source) and the executor committing, the consistency manager's create-segment event was a no-op (the bucket was already STALE) and clearValid then flipped it to VALID-empty -- a terminal in-coverage state nothing revisits -- silently dropping the backfilled rows (empty MV results), not self-healing. - MaterializedViewPartitionManager#clearValid now takes a Supplier and re-reads the source fingerprint inside the CAS mutator on every attempt; when the source is no longer empty it leaves the bucket STALE (no-op) so the next scheduler cycle re-materializes it via OVERWRITE. The in-mutator re-read closes the dominant, contention-driven window the pre-fix snapshot-before-loop left open. - MaterializedViewTaskExecutor#executeDeleteTask early-aborts the segment delete on backfill (avoids a transient empty-results window) and passes the re-read supplier to clearValid; the source-fingerprint read is extracted into a shared computeSourceWindowFingerprint helper reused by the APPEND/OVERWRITE validation. - MaterializedViewTaskScheduler stamps SOURCE_TABLE_NAME_KEY on DELETE tasks so the executor can recompute the source fingerprint at commit; corrected stale comments that said DELETE removes the partition entry. A narrow, irreducible cross-ZK residual window remains (documented in clearValid); a follow-up will close it by having the consistency manager re-mark in-coverage VALID-empty buckets STALE. Tests: clearValid backfill no-op + in-CAS re-read on retry; scheduler DELETE config carries the source table; executor abort-criterion branches. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Completes the DELETE-vs-backfill hardening and resolves the Copilot review feedback.
VALID-empty re-evaluation sweep (closes the residual race):
The executor's clearValid commit guard closes the dominant DELETE-vs-backfill window,
but a vanishingly narrow residual remains (a backfill's debounced STALE mark firing as a
no-op, while the bucket is still STALE, strictly between clearValid's source re-read and
its VALID-empty write). MaterializedViewConsistencyManager now runs a low-frequency
periodic sweep (default 5m, cluster-tunable via
pinot.materialized.view.consistency.empty.sweep.interval.ms) that re-evaluates in-coverage
VALID-empty partitions against the current source and re-marks STALE any whose source
window has regained segments, so the scheduler re-materializes them via OVERWRITE without
waiting for a fresh base-table event.
- Reuses computeWindowFingerprint for the emptiness check (no second overlap impl).
- Skips the source-segment read for MVs with no in-coverage VALID-empty buckets.
- Routes the re-mark through MaterializedViewPartitionManager#markStale (version-checked
CAS), so the advisory sweep snapshot is re-validated under the write.
- Runs on the existing single-threaded scheduler; per-MV failures are isolated.
Review-comment fixes:
- computeWindowFingerprint now returns the PartitionFingerprint.EMPTY constant for empty
overlap (matches the documented contract; avoids a per-call allocation; byte-identical
to the prior hash-of-empty-input result).
- enumerateCandidateBuckets now iterates the existing partition keys in range instead of
every bucket slot, bounding allocation to the partition count (a full-range invalidation
with a small bucketMs and a large watermark no longer allocates ~watermarkMs/bucketMs
entries). Functionally identical — markStale already no-ops absent buckets.
- Cleaned up a stale updateMaterializedViewRuntime reference in an executor test comment.
Tests: findStrandedEmptyBuckets decision (source regained / still empty / non-empty-VALID /
STALE / past-watermark), end-to-end per-view re-mark, and per-MV failure isolation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Description
Hardens the MV partition-state engine: two correctness fixes, then consolidation of mutations and fingerprint logic.
Bugfix
DELETEremoved the runtime partition entry (VACANT).ConsistencyMgrdid not mark absent in-coverage bucketsSTALE, so backfill never triggeredOVERWRITE.DELETEnow leavesVALID+ empty fingerprint;ConsistencyMgrcan synthesizeVACANT → STALEfor in-coverage windows.watermarkMswas advanced towardnow - bufferMseven when the base table had no newer segments, so broker freshness (now - watermarkMs) could report stale MV data as fresh. Watermark advancement is now capped at the latest source segmentendTime.Refactor
MaterializedViewPartitionManager(replaces three ad-hoc retry loops).MaterializedViewTaskUtils#computeWindowFingerprintfor scheduler, executor, and consistency paths.Design reference
The full partition-state machine — state definitions, operations, allowed transitions, and how the consistency manager keeps the partition map in sync with base-table segment changes — is documented here, with the core tables inlined below for reviewer convenience:
Partition state definitions
VACANTPartitionInfoentry exists for this bucket key in the partition map. The bucket has not been recorded by the MV's per-partition state machine.VALIDPartitionInfoentry exists in the partition map withstate = VALIDand aPartitionFingerprint(segment count + farm-hash CRC, orPartitionFingerprint.EMPTY).STALEPartitionInfoentry exists in the partition map withstate = STALE. The MV's recorded fingerprint is known (or assumed) to no longer match the current base-table source for this bucket — typically becauseConsistencyMgrobserved a base-table change.Operation definitions
MinionTaskCycleMinionTaskCycle_APPENDVACANT → VALID>= watermarkMs); advanceswatermarkMs; fingerprint is computed over overlapping base-table segments (orPartitionFingerprint.EMPTYif the source window has no overlapping segments).MinionTaskCycle_OVERWRITESTALE → VALIDSTALEbucket whose source still has data; re-materializes MV segments via segment-lineage replace;watermarkMsunchanged; fingerprint = recomputed value.MinionTaskCycle_DELETESTALE → VALIDSTALEbucket whose source has been retention-deleted (segmentCount == 0); drops MV segments via segment-lineage replace (segmentsTo = []);watermarkMsunchanged; fingerprint =PartitionFingerprint.EMPTY. The entry is intentionally not removed from the map — keeping it asVALID-emptyeliminatesVACANTas a runtime state for processed windows so a later backfill flips through the standardVALID → STALE → OVERWRITEcycle.ConsistencyMgrConsistencyMgr_MARK_STALE* → STALEConsistencyMgr. A base-table segment add / update / delete event is run through a filter pipeline (pre-coverage check, post-coverage / watermark cap, existence + state check); surviving events drive this op. After the bug fix it covers bothVALID → STALEand in-coverageVACANT → STALE(synthesizing a freshSTALEentry).ManualManual_MARK_STALE* → STALEREFRESH MVfamily of admin commands:REFRESH_MV(whole MV),REFRESH_MV_RANGE(time range, with optional coverage extension belowmin(partKey)), andREFRESH_MV_PARTITION(single bucket). All three variants have identical per-partition semantics — they differ only in selection scope (which buckets are targeted and whetherVACANTbuckets are synthesized).Manual_DROP_MV* → VACANTDROP MATERIALIZED VIEW. Atomically deletes the runtime znode (and the table config / schema). Bypasses the per-partition state machine — partitions are not first markedSTALE; the entire entry tree disappears in a single ZooKeeper delete.State transition table
VACANTVALIDSTALEVACANTMinionTaskCycle_APPENDConsistencyMgr_MARK_STALE,Manual_MARK_STALEVALIDManual_DROP_MVConsistencyMgr_MARK_STALE,Manual_MARK_STALESTALEManual_DROP_MVMinionTaskCycle_OVERWRITE,MinionTaskCycle_DELETESync with base-table change
The full filter pipeline (pre-coverage, post-coverage / watermark cap, existence + state check) that
ConsistencyMgrapplies to base-segment add / update / delete events — including how it decides which buckets to enumerate and which transitions to emit — lives in the linked design doc.This PR is the first implementation pass that fully matches that document; the post-merge state machine has a single owner (
MaterializedViewPartitionManager) and one test surface that exercises every transition in the table above.Why introduce
MaterializedViewPartitionManagerBefore this PR the per-partition mutation logic was spread across three sites — the minion executor, the controller-side task scheduler, and the consistency manager — each shipping its own ~50-line CAS-write retry loop with its own retry budget, its own jittered backoff, and its own hand-rolled
Map<Long, PartitionInfo>copy/edit code:APPEND/OVERWRITE/DELETEtask commitDEFAULT_MAX_RUNTIME_UPDATE_ATTEMPTSSTALErevertMAX_PARTITION_STATE_PERSIST_RETRIES(8, fixed)MAX_MARK_RETRIES+ThreadLocalRandombackoffConcrete problems this caused:
APPENDwas a separate ZK write from the bucket insert in some refactor proposals; under concurrent writers this opens a window where the partition map andwatermarkMsdisagree, which in turn poisons the broker'snow - watermarkMs <= stalenessThresholdMscheck.existing.getState() == STALEbeforeOVERWRITE).PartitionInfoobjects directly, bypassing every invariant the manager would otherwise enforce.The new design replaces all three loops with one CAS engine (
MaterializedViewPartitionManager#applyMutation), exposes one public method per documented operation (appendValid/refreshValid/clearValid/revertValid/markStale/deletePartition), and bundles watermark advancement into the same atomic write that mutates the bucket. ThePartitionInfoconstructor is locked to package access so production code outside the metadata package physically cannot bypass the manager — tests opt in via a single named factoryPartitionInfo.forTesting(...)annotated@VisibleForTesting.Net effect: one retry budget, one backoff implementation, one set of preconditions, one place to add the next state. Future transitions (
VACANT → STALEsynthesize for in-coverage backfill, an explicitVALID-emptystate, etc.) become a one-method addition with predictable behavior, not a three-site coordination exercise.Bug fixes that landed alongside the refactor
Two separate correctness bugs were fixed as part of this pass — both touch the same partition-state machinery the manager now owns, and both now have regression tests in the manager's unit-test surface:
Watermark over-advance past latest source data. The MV scheduler kept advancing
watermarkMstowardnow - bufferMseven when the base table had stopped receiving new data, which made the broker's freshness checknow - watermarkMs <= stalenessThresholdMsreturntruefor stale MV data.watermarkMsis now capped at the latestendTimepresent in the source segments, so the staleness check reflects real data freshness instead of wall-clock drift.Backfill into a previously-deleted bucket silently dropped. When a
STALEbucket's source data was retention-deleted, the executor'sDELETEbranch removed the runtimePartitionInfoentry entirely. That left the bucket in theabsentstate, which the consistency manager'smarkStalepass deliberately skips, so any subsequent base-table backfill into that window never propagated to the MV. TheDELETEbranch now writesVALID + PartitionFingerprint.EMPTYinstead, so the bucket follows the standardVALID → STALE → OVERWRITEcycle on backfill.PartitionFingerprint.EMPTYis byte-identical to whatcomputeWindowFingerprintalready produces when the overlapping segment list is empty (both feed an empty input throughfarmHashFingerprint64), so existing ZK records remain comparable across rolling upgrades — no migration required.Other clean-ups bundled in
Two verbatim copies of the filter + sort +
farmHashFingerprint64window-fingerprint algorithm (one in the scheduler, one in the executor) are collapsed intoMaterializedViewTaskUtils#computeWindowFingerprint. Drift between the two copies would silently break the executor's commit-time fingerprint validation and produce tasks that retry forever without advancing the watermark — one of the worst classes of MV bugs to triage.Tests
MaterializedViewPartitionManagerTestexercises every public method, including CAS retry / version-conflict paths and precondition violations.PartitionInfo.forTestingfactory.airlineStats_mv_hardening_smokewith1mrefresh and exercised the segment-delete → STALE → executor-DELETE → backfill path; verified that theVALID-emptywrite keeps the bucket re-syncing on backfill, and that the watermark stops advancing once base-table ingestion stalls.Suggested labels
bugfix,refactor