yujun777 commented on code in PR #68390:
URL: https://github.com/apache/doris/pull/68390#discussion_r4083012340


##########
fe/fe-core/src/main/java/org/apache/doris/job/extensions/mtmv/MTMVTask.java:
##########
@@ -768,6 +749,9 @@ private IvmIncrRefreshResult 
executeSingleIvmAttempt(MTMVRefreshContext refreshC
         } catch (Exception e) {
             throw new JobException("IVM snapshot generation failed for mv=" + 
mtmv.getName(), e);
         }
+        // The requirement these partitions are read under, captured before 
the read inside doRefresh and
+        // recorded only if the refresh commits; see captureLatestEpochs.
+        Map<String, Long> capturedEpochs = 
captureLatestEpochs(Sets.newHashSet(needRefreshPartitions));

Review Comment:
   Implemented. The states are read once, and that one read decides both what 
has to be rebuilt and the value each batch may write back: 
`MTMVTask#executeIvmAttempt` keeps the plan-time `latestEpoch` per partition, 
and `#plannedCeiling` clamps every captured epoch to it. An invalidation 
landing between the routing decision and a batch's read therefore stays above 
the value that batch records, so the partition is still rebuilt by the next 
refresh instead of being written back as satisfied.
   
   What is still missing is a test that pins the clamp: the window is a race, 
so a deterministic one has to drive `commitCapturedEpochs` with a planned map 
and a higher captured map. I will add that.



##########
fe/fe-core/src/main/java/org/apache/doris/mtmv/MTMVPartitionState.java:
##########
@@ -61,6 +61,43 @@ public MTMVPartitionState(MTMVPartitionState other) {
         this.latestEpoch = other.latestEpoch;
     }
 
+    /**
+     * The state a partition gets when it is first aligned: never refreshed, 
one generation required.
+     *
+     * <p>Alignment only ever creates this pair, so "no entry yet" and "this 
pair" say the same thing
+     * about the past -- the partition was never marked and holds no rows.
+     */
+    public static MTMVPartitionState initial() {
+        return new MTMVPartitionState(0, 1);
+    }
+
+    /**
+     * Whether this partition holds rows that a metadata-only change of a base 
table has made unusable,
+     * so it has to be rebuilt rather than caught up incrementally.
+     *
+     * <p>{@code latestEpoch > refreshEpoch} alone does not say that: {@code 
refreshEpoch == 0} means the
+     * partition was never refreshed, so it holds no rows and its first 
refresh reads the current base
+     * tables anyway -- it cannot carry in rows that no longer exist. Calling 
that dirty would rebuild
+     * every partition of a fresh MV for nothing.
+     *
+     * <p>The rejected alternative asked the partition itself instead ({@code 
visibleVersion == 1} means
+     * "no data", and a rebuild would put {@code refreshEpoch} back to 0 for 
such a partition). It is
+     * better in one way -- it does not rebuild a partition that is empty 
because its base partitions are
+     * empty -- but it leaks silently as soon as an anti-join, {@code NOT IN} 
or {@code NOT EXISTS}
+     * incremental refresh exists: removing input rows does not always remove 
output rows (a left join
+     * filtered by {@code b.v IS NULL} becomes non-empty when the matching 
rows go away), and that change
+     * emits no row binlog. Betting the criterion on today's operator support 
is exactly what this state
+     * exists to avoid, so the conservative reading wins.
+     */
+    public boolean isDirty() {
+        return latestEpoch > refreshEpoch && refreshEpoch != 0;

Review Comment:
   Implemented, and taken further than the finding asks: the exemption is gone 
rather than guarded, so `isDirty()` is `latestEpoch > refreshEpoch`. The crash 
cut closes on its own -- `{0, 2}` is dirty because `2 > 0` -- and the 
in-progress flag that was briefly proposed for it is not needed.
   
   `MTMVTest#testPartitionStateIsDirtyWhenItIsBehindItsRequirement` pins the 
pairs, `(0, 1)` and `(0, 2)` among them, and 
`MTMVTaskTest#testBuildAttemptsEscalatesToCompleteWhenEveryPartitionNeedsARebuild`
 pins that `(0, 1)` counts as needing a rebuild -- under the old reading that 
case kept the incremental chain instead.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to