nastra commented on code in PR #16686:
URL: https://github.com/apache/iceberg/pull/16686#discussion_r3440832727


##########
core/src/main/java/org/apache/iceberg/ManifestFilterManager.java:
##########
@@ -279,14 +281,16 @@ SnapshotSummary.Builder 
buildSummary(Iterable<ManifestFile> manifests) {
   private void validateRequiredDeletes(ManifestFile... manifests) {
     if (failMissingDeletePaths) {
       Set<F> deletedFiles = deletedFiles(manifests);
-      ValidationException.check(
-          deletedFiles.containsAll(deleteFiles),
-          "Missing required files to delete: %s",
-          COMMA.join(
-              deleteFiles.stream()
-                  .filter(f -> !deletedFiles.contains(f))
-                  .map(ContentFile::location)
-                  .collect(Collectors.toList())));
+      synchronized (deleteFiles) {

Review Comment:
   it would be good to avoid the synchronized block. An alternative approach 
that avoids this would be 
   ```
   diff --git 
a/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java 
b/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
   index 7d146d9246..66ef96a658 100644
   --- a/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
   +++ b/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
   @@ -22,7 +22,9 @@ import java.io.IOException;
    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
   +import java.util.Queue;
    import java.util.Set;
   +import java.util.concurrent.ConcurrentLinkedQueue;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.atomic.AtomicInteger;
    import java.util.function.Supplier;
   @@ -82,18 +84,22 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
      private long minSequenceNumber = 0;
      private boolean failAnyDelete = false;
      private boolean failMissingDeletePaths = false;
   -  private int duplicateDeleteCount = 0;
      private boolean caseSensitive = true;
      private boolean allDeletesReferenceManifests = true;
      // this is only being used for the DeleteManifestFilterManager to detect 
orphaned DVs for removed
      // data file paths
      private Set<String> removedDataFilePaths = Sets.newHashSet();
    
   +  // thread-safe queue for files discovered via expression matching during 
parallel filtering,
   +  // drained into deleteFiles after the parallel phase completes
   +  private final Queue<F> pendingDeletes = new ConcurrentLinkedQueue<>();
   +
      // cache filtered manifests to avoid extra work when commits fail.
      private final Map<ManifestFile, ManifestFile> filteredManifests = 
Maps.newConcurrentMap();
    
   -  // tracking where files were deleted to validate retries quickly
   -  private final Map<ManifestFile, Iterable<F>> 
filteredManifestToDeletedFiles =
   +  // cached per-manifest filter results (deleted files and duplicate count) 
for summary building
   +  // and retry correctness
   +  private final Map<ManifestFile, FilterResult<F>> 
filteredManifestToDeletedFiles =
          Maps.newConcurrentMap();
    
      private final Supplier<ExecutorService> workerPoolSupplier;
   @@ -229,6 +235,11 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
                  filtered[index] = manifest;
                });
    
   +    // merge expression-matched files discovered during parallel filtering 
into deleteFiles
   +    for (F file = pendingDeletes.poll(); file != null; file = 
pendingDeletes.poll()) {
   +      deleteFiles.add(file);
   +    }
   +
        validateRequiredDeletes(filtered);
    
        return Arrays.asList(filtered);
   @@ -256,16 +267,16 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
    
        for (ManifestFile manifest : manifests) {
          PartitionSpec manifestSpec = 
specsById.get(manifest.partitionSpecId());
   -      Iterable<F> manifestDeletes = 
filteredManifestToDeletedFiles.get(manifest);
   -      if (manifestDeletes != null) {
   -        for (F file : manifestDeletes) {
   +      FilterResult<F> result = filteredManifestToDeletedFiles.get(manifest);
   +      if (result != null) {
   +        for (F file : result.deletedFiles()) {
              summaryBuilder.deletedFile(manifestSpec, file);
            }
   +
   +        
summaryBuilder.incrementDuplicateDeletes(result.duplicateDeleteCount());
          }
        }
    
   -    summaryBuilder.incrementDuplicateDeletes(duplicateDeleteCount);
   -
        return summaryBuilder;
      }
    
   @@ -305,9 +316,9 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
    
        if (manifests != null) {
          for (ManifestFile manifest : manifests) {
   -        Iterable<F> manifestDeletes = 
filteredManifestToDeletedFiles.get(manifest);
   -        if (manifestDeletes != null) {
   -          for (F file : manifestDeletes) {
   +        FilterResult<F> result = 
filteredManifestToDeletedFiles.get(manifest);
   +        if (result != null) {
   +          for (F file : result.deletedFiles()) {
                deletedFiles.add(file);
              }
            }
   @@ -351,8 +362,9 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
              replacedManifestsCount.decrementAndGet();
            }
    
   -        // remove the entry from the cache
   +        // remove the entries from the caches
            filteredManifests.remove(manifest);
   +        filteredManifestToDeletedFiles.remove(filtered);
          }
        }
      }
   @@ -501,6 +513,7 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
        // when this point is reached, there is at least one file that will be 
deleted in the
        // manifest. produce a copy of the manifest with all deleted files 
removed.
        Set<F> deletedFiles = newFileSet();
   +    AtomicInteger duplicateDeleteCount = new AtomicInteger(0);
    
        try {
          ManifestWriter<F> writer = newManifestWriter(reader.spec());
   @@ -533,16 +546,17 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
                        if (allRowsMatch) {
                          writer.delete(entry);
                          F fileCopy = file.copyWithoutStats();
   -                      // add the file here in case it was deleted using an 
expression. The
   -                      // DeleteManifestFilterManager will then remove its 
matching DV
   -                      deleteFiles.add(fileCopy);
   +                      // add the file to the pending queue so it can be 
merged into deleteFiles
   +                      // after the parallel phase. The 
DeleteManifestFilterManager will then
   +                      // remove its matching DV
   +                      pendingDeletes.add(fileCopy);
    
                          if (deletedFiles.contains(file)) {
                            LOG.warn(
                                "Deleting a duplicate path from manifest {}: 
{}",
                                manifest.path(),
                                file.location());
   -                        duplicateDeleteCount += 1;
   +                        duplicateDeleteCount.incrementAndGet();
                          } else {
                            // only add the file to deletes if it is a new 
delete
                            // this keeps the snapshot summary accurate for 
non-duplicate data
   @@ -565,7 +579,8 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
    
          // update caches
          filteredManifests.put(manifest, filtered);
   -      filteredManifestToDeletedFiles.put(filtered, deletedFiles);
   +      filteredManifestToDeletedFiles.put(
   +          filtered, new FilterResult<>(deletedFiles, 
duplicateDeleteCount.get()));
    
          return filtered;
    
   @@ -574,6 +589,8 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
        }
      }
    
   +  private record FilterResult<F>(Iterable<F> deletedFiles, int 
duplicateDeleteCount) {}
   +
      // an evaluator that checks whether rows in a file may/must match a given 
expression
      // this class first partially evaluates the provided expression using the 
partition tuple
      // and then checks the remaining part of the expression using metrics 
evaluators
   
   ```



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