JingsongLi commented on code in PR #7715:
URL: https://github.com/apache/paimon/pull/7715#discussion_r4291189295


##########
paimon-spark/paimon-spark-common/src/main/scala/org/apache/paimon/spark/procedure/SparkOrphanFilesClean.scala:
##########
@@ -69,29 +82,57 @@ case class SparkOrphanFilesClean(
     val usedManifestFiles = spark.sparkContext
       .parallelize(branches.asScala.toSeq, maxBranchParallelism)
       .mapPartitions(_.flatMap {
-        branch => safelyGetAllSnapshots(branch).asScala.map(snapshot => 
(branch, snapshot.toJson))
+        branch =>
+          val liveSnapshots = safelyGetLiveSnapshots(branch)
+          snapshotsIncludingTagsAndChangelogs(branch, 
liveSnapshots).asScala.map(
+            snapshot => (branch, snapshot.toJson, 
liveSnapshots.contains(snapshot)))
       })
       .repartition(parallelism)
       .flatMap {
-        case (branch, snapshotJson) =>
+        case (branch, snapshotJson, isLive) =>
           val usedFileBuffer = new ArrayBuffer[BranchAndManifestFile]()
           val usedFileConsumer =
             new Consumer[org.apache.paimon.utils.Pair[String, 
java.lang.Boolean]] {
               override def accept(pair: utils.Pair[String, 
java.lang.Boolean]): Unit = {
-                usedFileBuffer.append(BranchAndManifestFile(branch, 
pair.getLeft, pair.getRight))
+                usedFileBuffer.append(
+                  BranchAndManifestFile(
+                    branch,
+                    pair.getLeft,
+                    pair.getRight,
+                    isMissing = false,
+                    isLiveSnapshot = isLive))
               }
             }
+          val missingManifest = if (isLive) new AtomicBoolean(false) else null
           val snapshot = Snapshot.fromJson(snapshotJson)
-          collectWithoutDataFileWithManifestFlag(branch, snapshot, 
usedFileConsumer)
+          collectWithoutDataFileWithManifestFlag(
+            branch,
+            snapshot,
+            usedFileConsumer,
+            missingManifest)
+          if (missingManifest != null && missingManifest.get()) {
+            usedFileBuffer.append(
+              BranchAndManifestFile(branch, "", isManifestFile = false, 
isMissing = true))
+          }
           usedFileBuffer
       }
       .toDS()
       .cache()
+    cachedDatasets += usedManifestFiles
+
+    if (!usedManifestFiles.filter(_.isMissing).isEmpty) {
+      logWarning("Detected missing manifest during used-files collection, 
aborting clean.")
+      return (
+        spark.createDataset(
+          Seq((deletedFilesCountInLocal.get(), 
deletedFilesLenInBytesInLocal.get()))),
+        cachedDatasets.toSeq)
+    }
 
-    // find all data files
-    val dataFiles = usedManifestFiles
-      .filter(_.isManifestFile)
-      .distinct()
+    val dataFilesWithFlag = usedManifestFiles
+      .filter(f => f.isManifestFile && !f.isMissing)

Review Comment:
   [P1] Check missing metadata before filtering the data-reference branch
   
   This filter still discards the missing-manifest marker when only the 
data-reference branch is recomputed. The two branches of 
`usedFiles.union(dataFiles)` can reuse different shuffle outputs: if the 
metadata branch has already produced its output, loss of the reference caches 
and the manifest-grouping shuffle can rerun only the data branch from the 
surviving captured-snapshot shuffle. After concurrent commit/expiration removes 
the old manifest list, its `isMissing` record is filtered out here. The 
metadata branch reuses its completed output, so its later missing-marker check 
does not run again. The anti-join can then delete data files still referenced 
by the current snapshot.
   
   I reproduced this on `09a86e021779569d7ea79bd50f58729a26fec170` with Spark 
3.5.8, real commits/expiration, and selective cache/shuffle-loss injection. 
After preserving the completed metadata-branch output and invalidating only the 
manifest-grouping shuffle map output plus the two reference caches, Spark 
retried the grouping stage following `MetadataFetchFailedException`. 
`usedManifestFiles` contained missing markers, but `dataFilesWithFlag` was 
empty; cleanup returned `(2, 594)`, deleted both old live data files, and a 
subsequent table read failed with `FileNotFoundException`. A control that 
throws on `isMissing` before this filter aborted cleanup and preserved all 
three rows.
   
   Please check `isMissing` and fail before filtering out non-manifest records, 
or propagate the abort signal through this branch. Add a regression test that 
retains the completed metadata-branch shuffle output while recomputing the 
data-reference branch; the current test evicts all caches before execution and 
therefore reruns the other branch's guard.



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