fresh-borzoni commented on code in PR #3519:
URL: https://github.com/apache/fluss/pull/3519#discussion_r3473536685


##########
fluss-lake/fluss-lake-paimon/src/main/java/org/apache/fluss/lake/paimon/utils/DvTableReadableSnapshotRetriever.java:
##########
@@ -426,6 +416,112 @@ public ReadableSnapshotResult 
getReadableSnapshotAndOffsets(long tieredSnapshotI
                 earliestSnapshotIdToKeep);
     }
 
+    /**
+     * Lowers {@code earliestSnapshotIdToKeep} to also cover the base anchors 
of buckets that have
+     * no L0 files in the latest compacted snapshot.
+     *
+     * <p>A bucket without L0 has all of its data in base files, and its 
readable offset is taken
+     * from the latest tiered snapshot. However, that base was established by 
the bucket's most
+     * recent flush, and a later recomputation (once the bucket receives new 
L0) will trace back to
+     * that flush's anchor snapshot (the previous APPEND of the latest 
snapshot that exactly holds
+     * the flushed L0). That anchor snapshot must therefore stay retained 
until the bucket is
+     * flushed again.
+     *
+     * <p>This is best-effort: if a bucket's flush history has expired and its 
anchor cannot be
+     * determined, this conservatively returns {@link 
LakeCommitResult#KEEP_ALL_PREVIOUS} so that no
+     * snapshot is deleted, rather than risk deleting one that is still needed.
+     *
+     * @param bucketsWithoutL0 buckets with no L0 in the latest compacted 
snapshot
+     * @param flussTableBucketMapper mapper from Paimon partition-bucket to 
Fluss table bucket
+     * @param latestCompactedSnapshotId the latest compacted snapshot id 
(traversal start)
+     * @param earliestSnapshotId the earliest snapshot id still present in 
Paimon (traversal end)
+     * @param earliestSnapshotIdToKeep the current value computed from buckets 
with L0
+     * @return the tightened earliest snapshot id to keep
+     */
+    private long tightenEarliestSnapshotIdToKeepForBucketsWithoutL0(
+            Set<PaimonPartitionBucket> bucketsWithoutL0,
+            FlussTableBucketMapper flussTableBucketMapper,
+            long latestCompactedSnapshotId,
+            long earliestSnapshotId,
+            long earliestSnapshotIdToKeep)
+            throws IOException {
+        if (bucketsWithoutL0.isEmpty()) {
+            return earliestSnapshotIdToKeep;
+        }
+
+        // Only track buckets that map to a Fluss bucket; unmappable ones 
(e.g. a partition not in
+        // Fluss) never need recomputation and must not pin retention.
+        Set<PaimonPartitionBucket> bucketsToAnchor = new HashSet<>();
+        for (PaimonPartitionBucket bucket : bucketsWithoutL0) {
+            if (flussTableBucketMapper.toTableBucket(bucket) != null) {
+                bucketsToAnchor.add(bucket);
+            }
+        }
+        if (bucketsToAnchor.isEmpty()) {
+            return earliestSnapshotIdToKeep;
+        }
+        boolean allAnchorsResolved = true;
+
+        for (long currentSnapshotId = latestCompactedSnapshotId;
+                currentSnapshotId >= earliestSnapshotId && 
!bucketsToAnchor.isEmpty();
+                currentSnapshotId--) {
+            Snapshot currentSnapshot = 
snapshotManager.tryGetSnapshot(currentSnapshotId);
+            if (currentSnapshot == null
+                    || currentSnapshot.commitKind() != 
Snapshot.CommitKind.COMPACT) {
+                continue;
+            }
+            // The first flush encountered going backwards is the bucket's 
most recent flush.
+            for (PaimonPartitionBucket partitionBucket : 
getBucketsWithFlushedL0(currentSnapshot)) {
+                if (!bucketsToAnchor.remove(partitionBucket)) {
+                    continue;
+                }
+                Snapshot previousAppendSnapshot = 
findBaseAnchorAppendSnapshot(currentSnapshot);
+                if (previousAppendSnapshot == null) {
+                    // can't determine this bucket's base anchor; don't 
tighten retention
+                    allAnchorsResolved = false;
+                    continue;
+                }
+                if (earliestSnapshotIdToKeep <= 0
+                        || previousAppendSnapshot.id() < 
earliestSnapshotIdToKeep) {
+                    earliestSnapshotIdToKeep = previousAppendSnapshot.id();
+                }
+            }
+        }
+
+        if (!bucketsToAnchor.isEmpty()) {
+            // some bucket's most recent flush was not found within the 
retained snapshots
+            allAnchorsResolved = false;
+        }
+
+        return allAnchorsResolved ? earliestSnapshotIdToKeep : 
LakeCommitResult.KEEP_ALL_PREVIOUS;

Review Comment:
   If one bucket's flush history has aged out, this returns KEEP_ALL_PREVIOUS 
and throws away the bound the other buckets already gave us - so retention 
never shrinks while that bucket stays cold. 
   
   The unresolvable anchor is by definition older than earliestSnapshotId 
(already gone from Paimon), so keep-all can't recover it anyway, flooring at 
earliestSnapshotId is just as safe and lets retention track Paimon's expiry. 
   
   WDYT?



##########
fluss-lake/fluss-lake-paimon/src/main/java/org/apache/fluss/lake/paimon/utils/DvTableReadableSnapshotRetriever.java:
##########
@@ -426,6 +416,112 @@ public ReadableSnapshotResult 
getReadableSnapshotAndOffsets(long tieredSnapshotI
                 earliestSnapshotIdToKeep);
     }
 
+    /**
+     * Lowers {@code earliestSnapshotIdToKeep} to also cover the base anchors 
of buckets that have
+     * no L0 files in the latest compacted snapshot.
+     *
+     * <p>A bucket without L0 has all of its data in base files, and its 
readable offset is taken
+     * from the latest tiered snapshot. However, that base was established by 
the bucket's most
+     * recent flush, and a later recomputation (once the bucket receives new 
L0) will trace back to
+     * that flush's anchor snapshot (the previous APPEND of the latest 
snapshot that exactly holds
+     * the flushed L0). That anchor snapshot must therefore stay retained 
until the bucket is
+     * flushed again.
+     *
+     * <p>This is best-effort: if a bucket's flush history has expired and its 
anchor cannot be
+     * determined, this conservatively returns {@link 
LakeCommitResult#KEEP_ALL_PREVIOUS} so that no
+     * snapshot is deleted, rather than risk deleting one that is still needed.
+     *
+     * @param bucketsWithoutL0 buckets with no L0 in the latest compacted 
snapshot
+     * @param flussTableBucketMapper mapper from Paimon partition-bucket to 
Fluss table bucket
+     * @param latestCompactedSnapshotId the latest compacted snapshot id 
(traversal start)
+     * @param earliestSnapshotId the earliest snapshot id still present in 
Paimon (traversal end)
+     * @param earliestSnapshotIdToKeep the current value computed from buckets 
with L0
+     * @return the tightened earliest snapshot id to keep
+     */
+    private long tightenEarliestSnapshotIdToKeepForBucketsWithoutL0(
+            Set<PaimonPartitionBucket> bucketsWithoutL0,
+            FlussTableBucketMapper flussTableBucketMapper,
+            long latestCompactedSnapshotId,
+            long earliestSnapshotId,
+            long earliestSnapshotIdToKeep)
+            throws IOException {
+        if (bucketsWithoutL0.isEmpty()) {
+            return earliestSnapshotIdToKeep;
+        }
+
+        // Only track buckets that map to a Fluss bucket; unmappable ones 
(e.g. a partition not in
+        // Fluss) never need recomputation and must not pin retention.
+        Set<PaimonPartitionBucket> bucketsToAnchor = new HashSet<>();
+        for (PaimonPartitionBucket bucket : bucketsWithoutL0) {
+            if (flussTableBucketMapper.toTableBucket(bucket) != null) {
+                bucketsToAnchor.add(bucket);
+            }
+        }
+        if (bucketsToAnchor.isEmpty()) {
+            return earliestSnapshotIdToKeep;
+        }
+        boolean allAnchorsResolved = true;
+
+        for (long currentSnapshotId = latestCompactedSnapshotId;
+                currentSnapshotId >= earliestSnapshotId && 
!bucketsToAnchor.isEmpty();
+                currentSnapshotId--) {
+            Snapshot currentSnapshot = 
snapshotManager.tryGetSnapshot(currentSnapshotId);
+            if (currentSnapshot == null
+                    || currentSnapshot.commitKind() != 
Snapshot.CommitKind.COMPACT) {
+                continue;
+            }
+            // The first flush encountered going backwards is the bucket's 
most recent flush.
+            for (PaimonPartitionBucket partitionBucket : 
getBucketsWithFlushedL0(currentSnapshot)) {

Review Comment:
   This re-walks the same snapshots as the with-L0 loop above, so 
getBucketsWithFlushedL0 scans manifests twice per compacted snapshot. Could it 
fold into that loop: one pass, offsets for the with-L0 buckets, anchor-only for 
the rest?



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