amogh-jahagirdar commented on code in PR #16686:
URL: https://github.com/apache/iceberg/pull/16686#discussion_r3446852410


##########
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:
   Huge +1 to getting rid of this synchronized.
   I pasted a diff above as well, but I think we can consolidate the duplicate 
and deletedFiles tracking, and and essentially not even populate deleteFiles in 
a multi-threaded manner and avoid this issue entirely. The usage of parallelism 
on that path is largely for I/O anyways.
   
   I also think we don't even need the concurrent linked queue state for 
tracking the files deleted by expresssion. I think once we figure out the 
manifests that need to be rewritten, we already have all of the deleted files, 
we can add those back to the global deleteFiles after the parallel read.



##########
core/src/main/java/org/apache/iceberg/ManifestFilterManager.java:
##########
@@ -95,6 +95,8 @@ public String partition() {
   // tracking where files were deleted to validate retries quickly
   private final Map<ManifestFile, Iterable<F>> filteredManifestToDeletedFiles =
       Maps.newConcurrentMap();
+  private final Map<ManifestFile, Integer> 
filteredManifestToDuplicateDeleteCounts =
+      Maps.newConcurrentMap();
 

Review Comment:
   ```
   --- a/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
   +++ b/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
   @@ -82,7 +82,6 @@ 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
   @@ -93,8 +92,7 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
      private final Map<ManifestFile, ManifestFile> filteredManifests = 
Maps.newConcurrentMap();
    
      // tracking where files were deleted to validate retries quickly
   -  private final Map<ManifestFile, Iterable<F>> 
filteredManifestToDeletedFiles =
   -      Maps.newConcurrentMap();
   +  private final Map<ManifestFile, FilterResult<F>> filteredManifestResults 
= Maps.newConcurrentMap();
    
      private final Supplier<ExecutorService> workerPoolSupplier;
    
   @@ -229,6 +227,15 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
                  filtered[index] = manifest;
                });
    
   +    // merge per-manifest deletes into deleteFiles after the parallel 
phase, avoiding concurrent
   +    // mutation of deleteFiles during filtering
   +    for (ManifestFile manifest : filtered) {
   +      FilterResult<F> result = filteredManifestResults.get(manifest);
   +      if (result != null) {
   +        result.deletedFiles().forEach(deleteFiles::add);
   +      }
   +    }
   +
        validateRequiredDeletes(filtered);
    
        return Arrays.asList(filtered);
   @@ -256,16 +263,15 @@ 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 = filteredManifestResults.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 +311,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 = filteredManifestResults.get(manifest);
   +        if (result != null) {
   +          for (F file : result.deletedFiles()) {
                deletedFiles.add(file);
              }
            }
   @@ -353,6 +359,7 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
    
            // remove the entry from the cache
            filteredManifests.remove(manifest);
   +        filteredManifestResults.remove(filtered);
          }
        }
      }
   @@ -501,6 +508,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 +541,13 @@ 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);
    
                          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 +570,8 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
    
          // update caches
          filteredManifests.put(manifest, filtered);
   -      filteredManifestToDeletedFiles.put(filtered, deletedFiles);
   +      filteredManifestResults.put(
   +          filtered, new FilterResult<>(deletedFiles, 
duplicateDeleteCount.get()));
    
          return filtered;
    
   @@ -574,6 +580,8 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
        }
      }
    
   +  private record FilterResult<T>(Iterable<T> 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
   
   ```
   
   here's the diff I came up with that I think addresses this issue as well 



##########
core/src/main/java/org/apache/iceberg/ManifestFilterManager.java:
##########
@@ -95,6 +95,8 @@ public String partition() {
   // tracking where files were deleted to validate retries quickly
   private final Map<ManifestFile, Iterable<F>> filteredManifestToDeletedFiles =
       Maps.newConcurrentMap();
+  private final Map<ManifestFile, Integer> 
filteredManifestToDuplicateDeleteCounts =
+      Maps.newConcurrentMap();
 

Review Comment:
   Instead of 2 separate maps I think we can have a bit more simplified of a 
state by having a single map and the value is a pair or record type with the 
Iterable and duplicateDelete counts.



##########
core/src/main/java/org/apache/iceberg/ManifestFilterManager.java:
##########
@@ -95,6 +95,8 @@ public String partition() {
   // tracking where files were deleted to validate retries quickly
   private final Map<ManifestFile, Iterable<F>> filteredManifestToDeletedFiles =
       Maps.newConcurrentMap();
+  private final Map<ManifestFile, Integer> 
filteredManifestToDuplicateDeleteCounts =
+      Maps.newConcurrentMap();
 

Review Comment:
   Maybe use a class instead of the record type, this seems like something we'd 
want to backport and older versions would need to be JDK 11 compliant etc.



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