This is an automated email from the ASF dual-hosted git repository.

huaxingao pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new c31dd921e1 Core: populate manifest created/replaced/kept count when 
commit a snapshot (#15003)
c31dd921e1 is described below

commit c31dd921e1550c643a3ea1fce9d9573cc06e2607
Author: Hongyue/Steve Zhang <[email protected]>
AuthorDate: Thu Feb 19 11:09:41 2026 -0800

    Core: populate manifest created/replaced/kept count when commit a snapshot 
(#15003)
    
    * Core: populate manifest created/kept count when commit a snapshot
    
    * Also handle replaced-manifest in SnapshotSummary.
    
    * Refactor manifest count handling in SnapshotSummary
    
     update to use AtomicInteger for replaced manifests count in 
ManifestFilterManager and ManifestMergeManager.
    
    * Enhance manifest replacement tracking in ManifestMergeManager
    
    Updated the logic for counting replaced manifests during bin-packing to 
only decrement the count for manifests from previous snapshots.
    
    * Only increment kept-manifest count if snapshot id is assigned to a 
manifest
    
    * Remove redundant tests for manifest metrics in TestCommitReporting and 
TestDeleteFiles, and enhance snapshot summary validation in TestTransaction.
    
    * lint
---
 .../main/java/org/apache/iceberg/FastAppend.java   |   2 +
 .../org/apache/iceberg/ManifestFilterManager.java  |  24 ++++-
 .../org/apache/iceberg/ManifestMergeManager.java   |  34 ++++++-
 .../apache/iceberg/MergingSnapshotProducer.java    |  14 +++
 .../java/org/apache/iceberg/SnapshotProducer.java  |  28 ++++++
 .../org/apache/iceberg/TestCommitReporting.java    |  18 ++++
 .../java/org/apache/iceberg/TestDeleteFiles.java   | 110 ++++++++++++++++++++-
 .../java/org/apache/iceberg/TestFastAppend.java    |  33 ++++++-
 .../java/org/apache/iceberg/TestMergeAppend.java   |  43 ++++++--
 .../test/java/org/apache/iceberg/TestRowDelta.java |  46 ++++++++-
 .../org/apache/iceberg/TestSnapshotSummary.java    |  77 ++++++++++-----
 .../java/org/apache/iceberg/TestTransaction.java   |  12 +++
 12 files changed, 397 insertions(+), 44 deletions(-)

diff --git a/core/src/main/java/org/apache/iceberg/FastAppend.java 
b/core/src/main/java/org/apache/iceberg/FastAppend.java
index 11459e0ecb..a6caed574a 100644
--- a/core/src/main/java/org/apache/iceberg/FastAppend.java
+++ b/core/src/main/java/org/apache/iceberg/FastAppend.java
@@ -166,6 +166,8 @@ class FastAppend extends SnapshotProducer<AppendFiles> 
implements AppendFiles {
       manifests.addAll(snapshot.allManifests(ops().io()));
     }
 
+    summaryBuilder.merge(buildManifestCountSummary(manifests, 0));
+
     return manifests;
   }
 
diff --git a/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java 
b/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
index 9b5dce4467..7d146d9246 100644
--- a/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
+++ b/core/src/main/java/org/apache/iceberg/ManifestFilterManager.java
@@ -24,6 +24,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.apache.iceberg.exceptions.RuntimeIOException;
@@ -74,6 +75,9 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
   private final Set<String> manifestsWithDeletes = Sets.newHashSet();
   private final PartitionSet dropPartitions;
   private final CharSequenceSet deletePaths = CharSequenceSet.empty();
+  // count of manifests that were rewritten with different manifest entry 
status during filtering
+  private final AtomicInteger replacedManifestsCount = new AtomicInteger(0);
+
   private Expression deleteExpression = Expressions.alwaysFalse();
   private long minSequenceNumber = 0;
   private boolean failAnyDelete = false;
@@ -313,6 +317,18 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
     return deletedFiles;
   }
 
+  /**
+   * Returns the count of manifests that were replaced (rewritten) during 
filtering.
+   *
+   * <p>A manifest is considered replaced when a new manifest was created to 
replace the original
+   * one (i.e., the original manifest != filtered manifest).
+   *
+   * @return the count of replaced manifests
+   */
+  int replacedManifestsCount() {
+    return replacedManifestsCount.get();
+  }
+
   /**
    * Deletes filtered manifests that were created by this class, but are not 
in the committed
    * manifest set.
@@ -329,9 +345,10 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
       ManifestFile manifest = entry.getKey();
       ManifestFile filtered = entry.getValue();
       if (!committed.contains(filtered)) {
-        // only delete if the filtered copy was created
+        // only delete if the filtered copy was created (manifest was replaced)
         if (!manifest.equals(filtered)) {
           deleteFile(filtered.path());
+          replacedManifestsCount.decrementAndGet();
         }
 
         // remove the entry from the cache
@@ -342,6 +359,7 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
 
   private void invalidateFilteredCache() {
     cleanUncommitted(SnapshotProducer.EMPTY_SET);
+    replacedManifestsCount.set(0);
   }
 
   /**
@@ -367,7 +385,9 @@ abstract class ManifestFilterManager<F extends 
ContentFile<F>> {
       // manifest without copying data. if a manifest does have a file to 
remove, this will break
       // out of the loop and move on to filtering the manifest.
       if (manifestHasDeletedFiles(evaluator, manifest, reader)) {
-        return filterManifestWithDeletedFiles(evaluator, manifest, reader);
+        ManifestFile filtered = filterManifestWithDeletedFiles(evaluator, 
manifest, reader);
+        replacedManifestsCount.incrementAndGet();
+        return filtered;
       } else {
         filteredManifests.put(manifest, manifest);
         return manifest;
diff --git a/core/src/main/java/org/apache/iceberg/ManifestMergeManager.java 
b/core/src/main/java/org/apache/iceberg/ManifestMergeManager.java
index 94eb8a1107..410edcc068 100644
--- a/core/src/main/java/org/apache/iceberg/ManifestMergeManager.java
+++ b/core/src/main/java/org/apache/iceberg/ManifestMergeManager.java
@@ -26,6 +26,7 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Supplier;
 import org.apache.iceberg.ManifestEntry.Status;
 import org.apache.iceberg.exceptions.RuntimeIOException;
@@ -43,6 +44,8 @@ abstract class ManifestMergeManager<F extends ContentFile<F>> 
{
   private final int minCountToMerge;
   private final boolean mergeEnabled;
 
+  // track manifests replaced during bin-packing
+  private final AtomicInteger replacedManifestsCount = new AtomicInteger(0);
   // cache merge results to reuse when retrying
   private final Map<List<ManifestFile>, ManifestFile> mergedManifests = 
Maps.newConcurrentMap();
 
@@ -86,6 +89,18 @@ abstract class ManifestMergeManager<F extends 
ContentFile<F>> {
     return merged;
   }
 
+  /**
+   * Returns the count of manifests that were replaced (merged) during 
bin-packing.
+   *
+   * <p>When multiple manifests are merged into a single manifest, each of the 
original manifests is
+   * considered replaced.
+   *
+   * @return the count of replaced manifests
+   */
+  int replacedManifestsCount() {
+    return replacedManifestsCount.get();
+  }
+
   void cleanUncommitted(Set<ManifestFile> committed) {
     // iterate over a copy of entries to avoid concurrent modification
     List<Map.Entry<List<ManifestFile>, ManifestFile>> entries =
@@ -96,8 +111,13 @@ abstract class ManifestMergeManager<F extends 
ContentFile<F>> {
       ManifestFile merged = entry.getValue();
       if (!committed.contains(merged)) {
         deleteFile(merged.path());
-        // remove the deleted file from the cache
-        mergedManifests.remove(entry.getKey());
+        List<ManifestFile> bin = entry.getKey();
+        mergedManifests.remove(bin);
+        for (ManifestFile m : bin) {
+          if (snapshotId() != m.snapshotId()) {
+            replacedManifestsCount.decrementAndGet();
+          }
+        }
       }
     }
   }
@@ -152,7 +172,7 @@ abstract class ManifestMergeManager<F extends 
ContentFile<F>> {
                 // not enough to merge, add all manifest files to the output 
list
                 outputManifests.addAll(bin);
               } else {
-                // merge the group
+                // merge the bin into a single manifest
                 outputManifests.add(createManifest(specId, bin));
               }
             });
@@ -200,8 +220,14 @@ abstract class ManifestMergeManager<F extends 
ContentFile<F>> {
 
     ManifestFile manifest = writer.toManifestFile();
 
-    // update the cache
+    // cache the merged manifest to reuse when retrying and track replaced 
manifests
     mergedManifests.put(bin, manifest);
+    for (ManifestFile m : bin) {
+      // only count manifests from previous snapshots; in-memory manifests are 
not replaced
+      if (snapshotId() != m.snapshotId()) {
+        replacedManifestsCount.incrementAndGet();
+      }
+    }
 
     return manifest;
   }
diff --git a/core/src/main/java/org/apache/iceberg/MergingSnapshotProducer.java 
b/core/src/main/java/org/apache/iceberg/MergingSnapshotProducer.java
index 761f94a830..79dcec3411 100644
--- a/core/src/main/java/org/apache/iceberg/MergingSnapshotProducer.java
+++ b/core/src/main/java/org/apache/iceberg/MergingSnapshotProducer.java
@@ -962,6 +962,20 @@ abstract class MergingSnapshotProducer<ThisT> extends 
SnapshotProducer<ThisT> {
     Iterables.addAll(manifests, 
mergeManager.mergeManifests(unmergedManifests));
     Iterables.addAll(manifests, 
deleteMergeManager.mergeManifests(unmergedDeleteManifests));
 
+    // update created/kept/replaced manifest count
+    // replaced manifests come from:
+    // 1. filterManager - manifests rewritten to remove deleted files
+    // 2. deleteFilterManager - delete manifests rewritten to remove deleted 
files
+    // 3. mergeManager - data manifests merged via bin-packing
+    // 4. deleteMergeManager - delete manifests merged via bin-packing
+    // Note: rewrittenAppendManifests are NEW manifests (copies), not replaced 
ones
+    int replacedManifestsCount =
+        filterManager.replacedManifestsCount()
+            + deleteFilterManager.replacedManifestsCount()
+            + mergeManager.replacedManifestsCount()
+            + deleteMergeManager.replacedManifestsCount();
+    summaryBuilder.merge(buildManifestCountSummary(manifests, 
replacedManifestsCount));
+
     return manifests;
   }
 
diff --git a/core/src/main/java/org/apache/iceberg/SnapshotProducer.java 
b/core/src/main/java/org/apache/iceberg/SnapshotProducer.java
index a8f28855ab..cbae25132d 100644
--- a/core/src/main/java/org/apache/iceberg/SnapshotProducer.java
+++ b/core/src/main/java/org/apache/iceberg/SnapshotProducer.java
@@ -645,6 +645,34 @@ abstract class SnapshotProducer<ThisT> implements 
SnapshotUpdate<ThisT> {
     return true;
   }
 
+  /**
+   * Builds a snapshot summary with manifest counts.
+   *
+   * @param manifests the list of manifests in the new snapshot
+   * @param replacedManifestsCount the count of manifests that were replaced 
(rewritten)
+   * @return a summary builder with manifest count metrics set
+   */
+  protected SnapshotSummary.Builder buildManifestCountSummary(
+      List<ManifestFile> manifests, int replacedManifestsCount) {
+    SnapshotSummary.Builder summaryBuilder = SnapshotSummary.builder();
+    int manifestsCreated = 0;
+    int manifestsKept = 0;
+
+    for (ManifestFile manifest : manifests) {
+      if (snapshotId() == manifest.snapshotId()) {
+        manifestsCreated++;
+      } else if (null != manifest.snapshotId()) {
+        manifestsKept++;
+      }
+    }
+
+    summaryBuilder.set(SnapshotSummary.CREATED_MANIFESTS_COUNT, 
String.valueOf(manifestsCreated));
+    summaryBuilder.set(SnapshotSummary.KEPT_MANIFESTS_COUNT, 
String.valueOf(manifestsKept));
+    summaryBuilder.set(
+        SnapshotSummary.REPLACED_MANIFESTS_COUNT, 
String.valueOf(replacedManifestsCount));
+    return summaryBuilder;
+  }
+
   protected List<ManifestFile> writeDataManifests(Collection<DataFile> files, 
PartitionSpec spec) {
     return writeDataManifests(files, null /* inherit data seq */, spec);
   }
diff --git a/core/src/test/java/org/apache/iceberg/TestCommitReporting.java 
b/core/src/test/java/org/apache/iceberg/TestCommitReporting.java
index d17348a99c..3d39dc6d38 100644
--- a/core/src/test/java/org/apache/iceberg/TestCommitReporting.java
+++ b/core/src/test/java/org/apache/iceberg/TestCommitReporting.java
@@ -63,6 +63,10 @@ public class TestCommitReporting extends TestBase {
     assertThat(metrics.addedFilesSizeInBytes().value()).isEqualTo(20L);
     assertThat(metrics.totalFilesSizeInBytes().value()).isEqualTo(20L);
 
+    assertThat(metrics.manifestsCreated().value()).isEqualTo(1L);
+    assertThat(metrics.manifestsKept().value()).isEqualTo(0L);
+    assertThat(metrics.manifestsReplaced().value()).isEqualTo(0L);
+
     // now remove those 2 data files
     table.newDelete().deleteFile(FILE_A).deleteFile(FILE_D).commit();
     report = reporter.lastCommitReport();
@@ -81,6 +85,11 @@ public class TestCommitReporting extends TestBase {
 
     assertThat(metrics.removedFilesSizeInBytes().value()).isEqualTo(20L);
     assertThat(metrics.totalFilesSizeInBytes().value()).isEqualTo(0L);
+
+    // delete rewrites the manifest to mark files as deleted: 1 created, 0 
kept, 1 replaced
+    assertThat(metrics.manifestsCreated().value()).isEqualTo(1L);
+    assertThat(metrics.manifestsKept().value()).isEqualTo(0L);
+    assertThat(metrics.manifestsReplaced().value()).isEqualTo(1L);
   }
 
   @TestTemplate
@@ -128,6 +137,10 @@ public class TestCommitReporting extends TestBase {
     
assertThat(metrics.addedFilesSizeInBytes().value()).isEqualTo(totalDeleteContentSize);
     
assertThat(metrics.totalFilesSizeInBytes().value()).isEqualTo(totalDeleteContentSize);
 
+    assertThat(metrics.manifestsCreated().value()).isEqualTo(1L);
+    assertThat(metrics.manifestsKept().value()).isEqualTo(0L);
+    assertThat(metrics.manifestsReplaced().value()).isEqualTo(0L);
+
     // now remove those 2 positional + 1 equality delete files
     table
         .newRewrite()
@@ -165,6 +178,11 @@ public class TestCommitReporting extends TestBase {
 
     
assertThat(metrics.removedFilesSizeInBytes().value()).isEqualTo(totalDeleteContentSize);
     assertThat(metrics.totalFilesSizeInBytes().value()).isEqualTo(0L);
+
+    // rewrite creates 1 manifest (delete manifest rewritten), keeps 0, 
replaces 1
+    assertThat(metrics.manifestsCreated().value()).isEqualTo(1L);
+    assertThat(metrics.manifestsKept().value()).isEqualTo(0L);
+    assertThat(metrics.manifestsReplaced().value()).isEqualTo(1L);
   }
 
   @TestTemplate
diff --git a/core/src/test/java/org/apache/iceberg/TestDeleteFiles.java 
b/core/src/test/java/org/apache/iceberg/TestDeleteFiles.java
index ea0988155b..fc08367d6b 100644
--- a/core/src/test/java/org/apache/iceberg/TestDeleteFiles.java
+++ b/core/src/test/java/org/apache/iceberg/TestDeleteFiles.java
@@ -97,6 +97,10 @@ public class TestDeleteFiles extends TestBase {
         table, 
table.newAppend().appendFile(FILE_A).appendFile(FILE_B).appendFile(FILE_C), 
branch);
     Snapshot append = latestSnapshot(readMetadata(), branch);
     assertThat(version()).isEqualTo(1);
+    assertThat(append.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
     validateSnapshot(null, append, FILE_A, FILE_B, FILE_C);
 
     commit(table, table.newDelete().deleteFile(FILE_A), branch);
@@ -104,6 +108,11 @@ public class TestDeleteFiles extends TestBase {
 
     assertThat(version()).isEqualTo(2);
     assertThat(delete1.allManifests(FILE_IO)).hasSize(1);
+    // delete rewrites manifest: 1 created, 0 kept, 1 replaced
+    assertThat(delete1.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         delete1.allManifests(table.io()).get(0),
         ids(delete1.snapshotId(), append.snapshotId(), append.snapshotId()),
@@ -113,6 +122,11 @@ public class TestDeleteFiles extends TestBase {
     Snapshot delete2 = commit(table, table.newDelete().deleteFile(FILE_B), 
branch);
     assertThat(version()).isEqualTo(3);
     assertThat(delete2.allManifests(FILE_IO)).hasSize(1);
+    // second delete rewrites manifest: 1 created, 0 kept, 1 replaced
+    assertThat(delete2.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         delete2.allManifests(FILE_IO).get(0),
         ids(delete2.snapshotId(), append.snapshotId()),
@@ -166,6 +180,10 @@ public class TestDeleteFiles extends TestBase {
             branch);
 
     assertThat(initialSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(initialSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
     validateManifestEntries(
         initialSnapshot.allManifests(FILE_IO).get(0),
         ids(initialSnapshot.snapshotId(), initialSnapshot.snapshotId()),
@@ -175,6 +193,10 @@ public class TestDeleteFiles extends TestBase {
     // delete the first data file
     Snapshot deleteSnapshot = commit(table, 
table.newDelete().deleteFile(firstDataFile), branch);
     assertThat(deleteSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(deleteSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         deleteSnapshot.allManifests(FILE_IO).get(0),
         ids(deleteSnapshot.snapshotId(), initialSnapshot.snapshotId()),
@@ -187,6 +209,10 @@ public class TestDeleteFiles extends TestBase {
         commit(table, 
table.newDelete().deleteFromRowFilter(Expressions.lessThan("id", 7)), branch);
 
     assertThat(finalSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(finalSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         finalSnapshot.allManifests(FILE_IO).get(0),
         ids(finalSnapshot.snapshotId()),
@@ -207,6 +233,10 @@ public class TestDeleteFiles extends TestBase {
             branch);
 
     assertThat(initialSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(initialSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
     validateManifestEntries(
         initialSnapshot.allManifests(FILE_IO).get(0),
         ids(initialSnapshot.snapshotId(), initialSnapshot.snapshotId()),
@@ -219,6 +249,10 @@ public class TestDeleteFiles extends TestBase {
             table, 
table.newDelete().deleteFromRowFilter(Expressions.greaterThan("id", 5)), 
branch);
 
     assertThat(deleteSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(deleteSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         deleteSnapshot.allManifests(FILE_IO).get(0),
         ids(initialSnapshot.snapshotId(), deleteSnapshot.snapshotId()),
@@ -239,6 +273,10 @@ public class TestDeleteFiles extends TestBase {
             branch);
 
     assertThat(initialSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(initialSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
     validateManifestEntries(
         initialSnapshot.allManifests(FILE_IO).get(0),
         ids(initialSnapshot.snapshotId(), initialSnapshot.snapshotId()),
@@ -252,6 +290,10 @@ public class TestDeleteFiles extends TestBase {
     Snapshot deleteSnapshot =
         commit(table, table.newDelete().deleteFromRowFilter(predicate), 
branch);
     assertThat(deleteSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(deleteSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         deleteSnapshot.allManifests(FILE_IO).get(0),
         ids(initialSnapshot.snapshotId(), deleteSnapshot.snapshotId()),
@@ -293,7 +335,12 @@ public class TestDeleteFiles extends TestBase {
 
   @TestTemplate
   public void testDeleteCaseSensitivity() {
-    commit(table, 
table.newFastAppend().appendFile(DATA_FILE_BUCKET_0_IDS_0_2), branch);
+    Snapshot append =
+        commit(table, 
table.newFastAppend().appendFile(DATA_FILE_BUCKET_0_IDS_0_2), branch);
+    assertThat(append.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     Expression rowFilter = Expressions.lessThan("iD", 5);
 
@@ -316,6 +363,10 @@ public class TestDeleteFiles extends TestBase {
             table, 
table.newDelete().deleteFromRowFilter(rowFilter).caseSensitive(false), branch);
 
     assertThat(deleteSnapshot.allManifests(FILE_IO)).hasSize(1);
+    assertThat(deleteSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         deleteSnapshot.allManifests(FILE_IO).get(0),
         ids(deleteSnapshot.snapshotId()),
@@ -328,13 +379,26 @@ public class TestDeleteFiles extends TestBase {
     String testBranch = "testBranch";
     
table.newAppend().appendFile(FILE_A).appendFile(FILE_B).appendFile(FILE_C).commit();
     Snapshot initialSnapshot = table.currentSnapshot();
+    assertThat(initialSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
+
     // Delete A on test branch
     table.newDelete().deleteFile(FILE_A).toBranch(testBranch).commit();
     Snapshot testBranchTip = table.snapshot(testBranch);
+    assertThat(testBranchTip.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
 
     // Delete B and C on main
     table.newDelete().deleteFile(FILE_B).deleteFile(FILE_C).commit();
     Snapshot delete2 = table.currentSnapshot();
+    assertThat(delete2.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
 
     // Verify B and C on testBranch
     validateManifestEntries(
@@ -403,9 +467,18 @@ public class TestDeleteFiles extends TestBase {
 
   @TestTemplate
   public void testDeleteValidateFileExistence() {
-    commit(table, table.newFastAppend().appendFile(FILE_B), branch);
+    Snapshot append = commit(table, table.newFastAppend().appendFile(FILE_B), 
branch);
+    assertThat(append.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
+
     Snapshot delete =
         commit(table, 
table.newDelete().deleteFile(FILE_B).validateFilesExist(), branch);
+    assertThat(delete.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         Iterables.getOnlyElement(delete.allManifests(FILE_IO)),
         ids(delete.snapshotId()),
@@ -432,15 +505,29 @@ public class TestDeleteFiles extends TestBase {
 
   @TestTemplate
   public void testDeleteFilesNoValidation() {
-    commit(table, table.newFastAppend().appendFile(FILE_B), branch);
+    Snapshot append = commit(table, table.newFastAppend().appendFile(FILE_B), 
branch);
+    assertThat(append.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
+
     Snapshot delete1 = commit(table, table.newDelete().deleteFile(FILE_B), 
branch);
+    assertThat(delete1.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateManifestEntries(
         Iterables.getOnlyElement(delete1.allManifests(FILE_IO)),
         ids(delete1.snapshotId()),
         files(FILE_B),
         statuses(Status.DELETED));
 
+    // deleting already deleted file results in no manifest changes
     Snapshot delete2 = commit(table, table.newDelete().deleteFile(FILE_B), 
branch);
+    assertThat(delete2.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
     assertThat(delete2.allManifests(FILE_IO)).isEmpty();
     assertThat(delete2.removedDataFiles(FILE_IO)).isEmpty();
   }
@@ -510,6 +597,10 @@ public class TestDeleteFiles extends TestBase {
     Snapshot snapshot = latestSnapshot(table, branch);
     assertThat(snapshot.sequenceNumber()).isEqualTo(1);
     assertThat(table.ops().current().lastSequenceNumber()).isEqualTo(1);
+    assertThat(snapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     // deleting by row filter should also remove the orphaned dv1 from delete 
manifests
     commit(table, 
table.newDelete().deleteFromRowFilter(Expressions.lessThan("id", 5)), branch);
@@ -517,6 +608,10 @@ public class TestDeleteFiles extends TestBase {
     Snapshot deleteSnap = latestSnapshot(table, branch);
     assertThat(deleteSnap.sequenceNumber()).isEqualTo(2);
     assertThat(table.ops().current().lastSequenceNumber()).isEqualTo(2);
+    assertThat(deleteSnap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "2");
 
     assertThat(deleteSnap.deleteManifests(table.io())).hasSize(1);
     validateDeleteManifest(
@@ -544,6 +639,10 @@ public class TestDeleteFiles extends TestBase {
     Snapshot snapshot = latestSnapshot(table, branch);
     assertThat(snapshot.sequenceNumber()).isEqualTo(1);
     assertThat(table.ops().current().lastSequenceNumber()).isEqualTo(1);
+    assertThat(snapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     // deleting by path should also remove the orphaned DV for fileA from 
delete manifests
     commit(table, table.newDelete().deleteFile(FILE_A.location()), branch);
@@ -551,6 +650,11 @@ public class TestDeleteFiles extends TestBase {
     Snapshot deleteSnap = latestSnapshot(table, branch);
     assertThat(deleteSnap.sequenceNumber()).isEqualTo(2);
     assertThat(table.ops().current().lastSequenceNumber()).isEqualTo(2);
+    // delete rewrites both data and delete manifests
+    assertThat(deleteSnap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "2");
 
     assertThat(deleteSnap.deleteManifests(table.io())).hasSize(1);
     validateDeleteManifest(
diff --git a/core/src/test/java/org/apache/iceberg/TestFastAppend.java 
b/core/src/test/java/org/apache/iceberg/TestFastAppend.java
index 33153d8454..8f427525e2 100644
--- a/core/src/test/java/org/apache/iceberg/TestFastAppend.java
+++ b/core/src/test/java/org/apache/iceberg/TestFastAppend.java
@@ -170,7 +170,11 @@ public class TestFastAppend extends TestBase {
     }
 
     // validate that the metadata summary is correct when using appendManifest
-    assertThat(snap.summary()).containsEntry("added-data-files", "2");
+    assertThat(snap.summary())
+        .containsEntry("added-data-files", "2")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
 
     V2Assert.assertEquals("Snapshot sequence number should be 1", 1, 
snap.sequenceNumber());
     V2Assert.assertEquals(
@@ -214,6 +218,13 @@ public class TestFastAppend extends TestBase {
       
assertThat(snap.allManifests(FILE_IO).get(1).path()).isEqualTo(manifest.path());
     }
 
+    // validate manifest metrics in the snapshot summary
+    // 2 manifests created: 1 for appendFile (FILE_C, FILE_D) + 1 for 
appendManifest
+    assertThat(snap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
+
     V2Assert.assertEquals("Snapshot sequence number should be 1", 1, 
snap.sequenceNumber());
     V2Assert.assertEquals(
         "Last sequence number should be 1", 1, 
readMetadata().lastSequenceNumber());
@@ -459,7 +470,10 @@ public class TestFastAppend extends TestBase {
         .containsEntry("added-data-files", "2")
         .containsEntry("added-records", "2")
         .containsEntry("total-data-files", "2")
-        .containsEntry("total-records", "2");
+        .containsEntry("total-records", "2")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -551,7 +565,10 @@ public class TestFastAppend extends TestBase {
 
     assertThat(table.currentSnapshot().summary())
         .doesNotContainKey(SnapshotSummary.PARTITION_SUMMARY_PROP)
-        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1");
+        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -571,7 +588,10 @@ public class TestFastAppend extends TestBase {
         .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1")
         .containsEntry(
             SnapshotSummary.CHANGED_PARTITION_PREFIX + "data_bucket=0",
-            "added-data-files=1,added-records=1,added-files-size=10");
+            "added-data-files=1,added-records=1,added-files-size=10")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -588,7 +608,10 @@ public class TestFastAppend extends TestBase {
 
     assertThat(table.currentSnapshot().summary())
         .doesNotContainKey(SnapshotSummary.PARTITION_SUMMARY_PROP)
-        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "2");
+        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "2")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
diff --git a/core/src/test/java/org/apache/iceberg/TestMergeAppend.java 
b/core/src/test/java/org/apache/iceberg/TestMergeAppend.java
index 0759b0f13a..4863aa37be 100644
--- a/core/src/test/java/org/apache/iceberg/TestMergeAppend.java
+++ b/core/src/test/java/org/apache/iceberg/TestMergeAppend.java
@@ -285,7 +285,11 @@ public class TestMergeAppend extends TestBase {
         statuses(Status.ADDED, Status.ADDED));
 
     // validate that the metadata summary is correct when using appendManifest
-    assertThat(committedSnapshot.summary()).containsEntry("added-data-files", 
"2");
+    assertThat(committedSnapshot.summary())
+        .containsEntry("added-data-files", "2")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -514,6 +518,12 @@ public class TestMergeAppend extends TestBase {
         ids(commitId1, commitId1),
         files(FILE_C, FILE_D),
         statuses(Status.ADDED, Status.ADDED));
+    // 3 manifests appended, bin-packing creates 2 bins: [m1] and [m2, m3]
+    // bin 1 kept as-is, bin 2 merged; first snapshot has no prior manifests
+    assertThat(snap1.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
 
     // produce new manifests as the old ones could have been compacted
     manifest = writeManifestWithName("FILE_A_S2", FILE_A);
@@ -559,8 +569,13 @@ public class TestMergeAppend extends TestBase {
         files(FILE_A, FILE_C, FILE_D),
         statuses(Status.EXISTING, Status.EXISTING, Status.EXISTING));
 
-    // validate that the metadata summary is correct when using appendManifest
-    assertThat(snap2.summary()).containsEntry("added-data-files", "3");
+    // 3 new manifests + 2 existing from snap1; bin-packing merges both groups
+    // 2 replaced = existing manifests from snap1 that were merged
+    assertThat(snap2.summary())
+        .containsEntry("added-data-files", "3")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "3")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -781,6 +796,13 @@ public class TestMergeAppend extends TestBase {
     V1Assert.assertEquals(
         "Table should end with last-sequence-number 0", 0, 
readMetadata().lastSequenceNumber());
 
+    // The delete operation rewrites the original manifest to mark FILE_A as 
deleted
+    // This should result in 1 replaced manifest, 1 created manifest, and 0 
kept manifests
+    assertThat(deleteSnapshot.summary())
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
+
     long deleteId = latestSnapshot(table, branch).snapshotId();
     assertThat(latestSnapshot(table, 
branch).allManifests(table.io())).hasSize(1);
     ManifestFile deleteManifest = 
deleteSnapshot.allManifests(table.io()).get(0);
@@ -1485,7 +1507,10 @@ public class TestMergeAppend extends TestBase {
 
     assertThat(table.currentSnapshot().summary())
         .doesNotContainKey(SnapshotSummary.PARTITION_SUMMARY_PROP)
-        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1");
+        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -1505,7 +1530,10 @@ public class TestMergeAppend extends TestBase {
         .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1")
         .containsEntry(
             SnapshotSummary.CHANGED_PARTITION_PREFIX + "data_bucket=0",
-            "added-data-files=1,added-records=1,added-files-size=10");
+            "added-data-files=1,added-records=1,added-files-size=10")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -1522,6 +1550,9 @@ public class TestMergeAppend extends TestBase {
 
     assertThat(table.currentSnapshot().summary())
         .doesNotContainKey(SnapshotSummary.PARTITION_SUMMARY_PROP)
-        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "2");
+        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "2")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 }
diff --git a/core/src/test/java/org/apache/iceberg/TestRowDelta.java 
b/core/src/test/java/org/apache/iceberg/TestRowDelta.java
index 4eff3a400f..59a73ba202 100644
--- a/core/src/test/java/org/apache/iceberg/TestRowDelta.java
+++ b/core/src/test/java/org/apache/iceberg/TestRowDelta.java
@@ -77,6 +77,10 @@ public class TestRowDelta extends TestBase {
     assertThat(snap.sequenceNumber()).isEqualTo(1);
     assertThat(snap.operation()).isEqualTo(DataOperations.DELETE);
     assertThat(snap.deleteManifests(table.io())).hasSize(1);
+    assertThat(snap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -88,6 +92,10 @@ public class TestRowDelta extends TestBase {
     assertThat(snap.sequenceNumber()).isEqualTo(1);
     assertThat(snap.operation()).isEqualTo(DataOperations.APPEND);
     assertThat(snap.dataManifests(table.io())).hasSize(1);
+    assertThat(snap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     validateManifest(
         snap.dataManifests(table.io()).get(0),
@@ -111,6 +119,10 @@ public class TestRowDelta extends TestBase {
         .as("Delta commit should use operation 'overwrite'")
         .isEqualTo(DataOperations.OVERWRITE);
     assertThat(snap.dataManifests(table.io())).hasSize(1);
+    assertThat(snap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     validateManifest(
         snap.dataManifests(table.io()).get(0),
@@ -132,7 +144,12 @@ public class TestRowDelta extends TestBase {
 
   @TestTemplate
   public void testAddRowsRemoveDataFile() {
-    table.newRowDelta().addRows(FILE_A).commit();
+    Snapshot firstSnap = commit(table, table.newRowDelta().addRows(FILE_A), 
branch);
+    assertThat(firstSnap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
+
     SnapshotUpdate<?> rowDelta = 
table.newRowDelta().addRows(FILE_B).removeRows(FILE_A);
 
     commit(table, rowDelta, branch);
@@ -143,6 +160,10 @@ public class TestRowDelta extends TestBase {
         .as("Delta commit should use operation 'overwrite'")
         .isEqualTo(DataOperations.OVERWRITE);
     assertThat(snap.dataManifests(table.io())).hasSize(2);
+    assertThat(snap.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
 
     validateManifest(
         snap.dataManifests(table.io()).get(0),
@@ -1070,6 +1091,10 @@ public class TestRowDelta extends TestBase {
         .containsEntry(TOTAL_DELETE_FILES_PROP, "3")
         .containsEntry(ADDED_POS_DELETES_PROP, String.valueOf(posDeletesCount))
         .containsEntry(TOTAL_POS_DELETES_PROP, String.valueOf(posDeletesCount))
+        // 4 created (1 data + 3 delete), 3 kept (prior data manifests)
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "4")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "3")
         .hasEntrySatisfying(
             CHANGED_PARTITION_PREFIX + "data_bucket=0",
             v -> assertThat(v).contains(ADDED_DELETE_FILES_PROP + "=1"))
@@ -1160,6 +1185,10 @@ public class TestRowDelta extends TestBase {
     // 2 appends and 1 row delta where delete files belong to different specs
     assertThat(thirdSnapshot.dataManifests(table.io())).hasSize(2);
     assertThat(thirdSnapshot.deleteManifests(table.io())).hasSize(2);
+    assertThat(thirdSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     // commit two more delete files to the same specs to trigger merging
     DeleteFile thirdDeleteFile = newDeletes(firstSnapshotDataFile);
@@ -1603,6 +1632,10 @@ public class TestRowDelta extends TestBase {
     RowDelta baseRowDelta = 
table.newRowDelta().addRows(dataFile).addDeletes(deleteFile);
     Snapshot baseSnapshot = commit(table, baseRowDelta, branch);
     assertThat(baseSnapshot.operation()).isEqualTo(DataOperations.OVERWRITE);
+    assertThat(baseSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     DeleteFile newDeleteFile = newDeletes(dataFile);
     RowDelta rowDelta =
@@ -1613,6 +1646,10 @@ public class TestRowDelta extends TestBase {
             .validateFromSnapshot(baseSnapshot.snapshotId());
     Snapshot snapshot = commit(table, rowDelta, branch);
     assertThat(snapshot.operation()).isEqualTo(DataOperations.DELETE);
+    assertThat(snapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
 
     List<ManifestFile> dataManifests = snapshot.dataManifests(table.io());
     assertThat(dataManifests).hasSize(1);
@@ -1912,8 +1949,13 @@ public class TestRowDelta extends TestBase {
     RowDelta rowDelta2 = table.newRowDelta().addDeletes(dv);
     Snapshot dvSnapshot = commit(table, rowDelta2, branch);
 
-    // both must be part of the table and merged into one manifest
+    // both delete files merged into one manifest
     ManifestFile deleteManifest = 
Iterables.getOnlyElement(dvSnapshot.deleteManifests(table.io()));
+    // 1 created (merged), 1 kept (data), 1 replaced (existing delete manifest)
+    assertThat(dvSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
     validateDeleteManifest(
         deleteManifest,
         dataSeqs(3L, 2L),
diff --git a/core/src/test/java/org/apache/iceberg/TestSnapshotSummary.java 
b/core/src/test/java/org/apache/iceberg/TestSnapshotSummary.java
index 1eee2d293e..61084b4e67 100644
--- a/core/src/test/java/org/apache/iceberg/TestSnapshotSummary.java
+++ b/core/src/test/java/org/apache/iceberg/TestSnapshotSummary.java
@@ -101,7 +101,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(11)
+        .hasSize(14)
         .containsEntry(SnapshotSummary.ADDED_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "10")
         .containsEntry(SnapshotSummary.ADDED_RECORDS_PROP, "1")
@@ -111,7 +111,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "10")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -126,7 +129,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(11)
+        .hasSize(14)
         .containsEntry(SnapshotSummary.ADDED_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "10")
         .containsEntry(SnapshotSummary.ADDED_RECORDS_PROP, "1")
@@ -136,7 +139,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "10")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -155,7 +161,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(14)
+        .hasSize(17)
         .containsEntry(SnapshotSummary.ADDED_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "10")
         .containsEntry(SnapshotSummary.ADDED_RECORDS_PROP, "1")
@@ -168,7 +174,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "10")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
   }
 
   @TestTemplate
@@ -187,7 +196,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(11)
+        .hasSize(14)
         .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "2")
         .containsEntry(SnapshotSummary.DELETED_FILES_PROP, "2")
         .containsEntry(SnapshotSummary.DELETED_RECORDS_PROP, "2")
@@ -197,7 +206,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "0")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "0");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "0")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
   }
 
   @TestTemplate
@@ -212,7 +224,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(12)
+        .hasSize(15)
         .containsEntry(SnapshotSummary.ADDED_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "10")
         .containsEntry(SnapshotSummary.ADDED_RECORDS_PROP, "1")
@@ -223,7 +235,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "10")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -238,7 +253,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(11)
+        .hasSize(14)
         .containsEntry(SnapshotSummary.ADDED_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "10")
         .containsEntry(SnapshotSummary.ADDED_RECORDS_PROP, "1")
@@ -248,7 +263,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "10")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -267,7 +285,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(14)
+        .hasSize(17)
         .containsEntry(SnapshotSummary.ADDED_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_DELETE_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "20") // size of 
data + delete file
@@ -280,7 +298,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "1")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "20")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -300,7 +321,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(14)
+        .hasSize(17)
         .containsEntry(SnapshotSummary.ADDED_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "10")
         .containsEntry(SnapshotSummary.ADDED_RECORDS_PROP, "1")
@@ -313,7 +334,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "10")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0");
   }
 
   @TestTemplate
@@ -334,7 +358,7 @@ public class TestSnapshotSummary extends TestBase {
         .commit();
 
     assertThat(table.currentSnapshot().summary())
-        .hasSize(16)
+        .hasSize(19)
         .containsEntry(SnapshotSummary.ADDED_DELETE_FILES_PROP, "1")
         .containsEntry(SnapshotSummary.ADDED_FILE_SIZE_PROP, "10")
         .containsEntry(SnapshotSummary.ADD_POS_DELETE_FILES_PROP, "1")
@@ -349,7 +373,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_POS_DELETES_PROP, "1")
         .containsEntry(SnapshotSummary.TOTAL_FILE_SIZE_PROP, "20")
-        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1");
+        .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "2")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
   }
 
   @TestTemplate
@@ -368,7 +395,7 @@ public class TestSnapshotSummary extends TestBase {
     long totalPosDeletes1 = dv1.recordCount() + dv2.recordCount();
     long totalFileSize1 = dv1.contentSizeInBytes() + dv2.contentSizeInBytes();
     assertThat(summary1)
-        .hasSize(12)
+        .hasSize(15)
         .doesNotContainKey(SnapshotSummary.ADD_POS_DELETE_FILES_PROP)
         .doesNotContainKey(SnapshotSummary.REMOVED_POS_DELETE_FILES_PROP)
         .containsEntry(SnapshotSummary.ADDED_DELETE_FILES_PROP, "1")
@@ -385,7 +412,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_DATA_FILES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "0")
-        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1");
+        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "1")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
 
     DeleteFile dv3 = newDV(FILE_A);
     table
@@ -404,7 +434,7 @@ public class TestSnapshotSummary extends TestBase {
     long totalPosDeletes2 = dv3.recordCount();
     long totalFileSize2 = dv3.contentSizeInBytes();
     assertThat(summary2)
-        .hasSize(16)
+        .hasSize(19)
         .doesNotContainKey(SnapshotSummary.ADD_POS_DELETE_FILES_PROP)
         .doesNotContainKey(SnapshotSummary.REMOVED_POS_DELETE_FILES_PROP)
         .containsEntry(SnapshotSummary.ADDED_DELETE_FILES_PROP, "1")
@@ -421,7 +451,10 @@ public class TestSnapshotSummary extends TestBase {
         .containsEntry(SnapshotSummary.TOTAL_DATA_FILES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_EQ_DELETES_PROP, "0")
         .containsEntry(SnapshotSummary.TOTAL_RECORDS_PROP, "0")
-        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "2");
+        .containsEntry(SnapshotSummary.CHANGED_PARTITION_COUNT_PROP, "2")
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "3")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "2");
   }
 
   @TestTemplate
diff --git a/core/src/test/java/org/apache/iceberg/TestTransaction.java 
b/core/src/test/java/org/apache/iceberg/TestTransaction.java
index 61e0b9ae5a..9ec8c47840 100644
--- a/core/src/test/java/org/apache/iceberg/TestTransaction.java
+++ b/core/src/test/java/org/apache/iceberg/TestTransaction.java
@@ -170,6 +170,18 @@ public class TestTransaction extends TestBase {
         ids(appendSnapshot.snapshotId(), appendSnapshot.snapshotId()),
         files(FILE_A, FILE_B),
         statuses(Status.ADDED, Status.ADDED));
+
+    // validate snapshot summaries for manifest metrics
+    assertThat(appendSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "0");
+
+    // delete rewrites the append manifest
+    assertThat(deleteSnapshot.summary())
+        .containsEntry(SnapshotSummary.CREATED_MANIFESTS_COUNT, "1")
+        .containsEntry(SnapshotSummary.KEPT_MANIFESTS_COUNT, "0")
+        .containsEntry(SnapshotSummary.REPLACED_MANIFESTS_COUNT, "1");
   }
 
   @TestTemplate

Reply via email to