danny0405 commented on code in PR #20273:
URL: https://github.com/apache/hudi/pull/20273#discussion_r4272609569


##########
hudi-common/src/main/java/org/apache/hudi/common/table/timeline/versioning/v2/CompletionTimeQueryViewV2.java:
##########
@@ -91,9 +102,19 @@ public class CompletionTimeQueryViewV2 implements 
CompletionTimeQueryView, Seria
   public CompletionTimeQueryViewV2(HoodieTableMetaClient metaClient) {
     this.metaClient = metaClient;
     this.instantTimeToCompletionTimeMap = new ConcurrentHashMap<>();
-    this.cursorInstant = 
metaClient.getActiveTimeline().firstInstant().map(HoodieInstant::requestedTime).orElse("");
+    HoodieTimeline activeTimeline = metaClient.getActiveTimeline();
+    // Instants at or after the cursor are assumed to be on the active 
timeline, so it must start at the first
+    // write instant that is not savepointed: an older non-write instant (e.g. 
a rollback the archiver has not reached)
+    // or a savepointed commit left behind by archival would otherwise hide 
the archived write instants after it.
+    // Instants before the cursor that are still active are loaded eagerly by 
#load. Without such a write instant,
+    // nothing after the last active instant can be archived.
+    this.firstActiveWriteInstant = 
activeTimeline.getTimelineOfActions(HoodieTimeline.WRITE_AND_SAVEPOINT_ACTIONS).getFirstNonSavepointCommit()

Review Comment:
   [P2] Preserve the no-archive-I/O path for ranges covered by a contiguous 
active timeline
   
   Moving the cursor from the first active instant to the first non-savepointed 
write can introduce archive I/O even with 
`hoodie.archive.beyond.savepoint=false` and no archival holes. For example, let 
all archived instants precede Oct 1, with an active completed clean on Oct 1 
and the first active write on Oct 3. For a query starting Oct 3 at noon, 
`getInstantTimes()` computes its one-day lookback as Oct 2 at noon. Previously 
that was after the cursor, so no archive load occurred; now it is before the 
cursor and calls `loadCompletionTimeIncrementally()` for an interval containing 
no archived instants. A leading sequence of retained savepointed writes can 
produce the same effect.
   
   `ArchivedTimelineLoaderV2.getFilteredFiles()` reads the latest archive 
version/manifest before discovering that no files overlap. This adds storage 
I/O and manifest processing, even though no archive Parquet file needs to be 
read. The cursor only amortizes this within one view; 
`IncrementalQueryAnalyzer.analyze()` creates a new view each time.
   
   Could we retain a safe fast path for contiguous V2 timelines while 
preserving the legacy-upgrade/hole fix, and add a loader-call-count regression 
test for this fully active range?



##########
hudi-common/src/main/java/org/apache/hudi/common/table/timeline/versioning/v2/CompletionTimeQueryViewV2.java:
##########
@@ -102,14 +123,18 @@ public CompletionTimeQueryViewV2(HoodieTableMetaClient 
metaClient) {
    */
   public boolean isCompleted(String instantTime) {
     // archival does not proceed beyond the first savepoint, so any instant 
before that is completed.
-    return this.instantTimeToCompletionTimeMap.containsKey(instantTime) || 
isArchived(instantTime);
+    return this.instantTimeToCompletionTimeMap.containsKey(instantTime)
+        || InstantComparison.compareTimestamps(instantTime, LESSER_THAN, 
this.firstNonSavepointCommit);
   }
 
   /**
    * Returns whether the instant is archived.
    */
   public boolean isArchived(String instantTime) {
-    return InstantComparison.compareTimestamps(instantTime, LESSER_THAN, 
this.firstNonSavepointCommit);
+    // with archival beyond savepoint, the write instants between a 
savepointed commit and the first active write
+    // instant are archived as well, while the savepointed commit itself stays 
active.
+    return InstantComparison.compareTimestamps(instantTime, LESSER_THAN, 
this.firstActiveWriteInstant)

Review Comment:
   [P2] Avoid archive lookup caused by an active clean/rollback before this 
boundary
   
   With archival beyond savepoint disabled, consider an entirely active 
sequence `C1(savepointed), clean2, C3`. This new boundary is C3, so 
`isArchived(clean2)` returns true even though clean2 is active. Previously the 
write boundary was C1 and it returned false. The same applies to a rollback 
between those writes.
   
   This reaches the query path because `load()` stores completion times for all 
completed actions, and the bounded `getInstantTimes()` path returns matching 
map entries without restricting them to writes. If clean2 is in the 
completion-time window, `splitInstantByActiveness()` now puts it in the 
archived bucket and `analyze()` calls `getArchivedReadTimeline()`, even when 
every selected write is active. Subsequent commit filtering removes the 
non-write action, so the concern here is unnecessary archive access rather than 
incorrect write rows. This can also happen independently of the cursor/lookback 
issue when a delayed clean/rollback completes in a recent query window.
   
   Could the analyzer exclude non-write entries from its archival decision (or 
otherwise recognize these active actions), with a test asserting that an active 
savepoint/clean/write sequence does not trigger an archived-timeline lookup?



-- 
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]

Reply via email to