mxm commented on code in PR #17113:
URL: https://github.com/apache/iceberg/pull/17113#discussion_r3541183016


##########
docs/docs/flink-maintenance.md:
##########
@@ -82,6 +82,49 @@ Used to remove files which are not referenced in any 
metadata files of an Iceber
     .deleteBatchSize(1000))
 ```
 
+#### ConvertEqualityDeletes
+Converts equality delete files staged on a source branch into deletion vectors 
(DVs) on the target branch. Requires table format version >= 3 (which 
introduces DVs). This is typically used together with the Flink `IcebergSink`, 
which writes new data files and equality deletes to a staging branch; the 
converter then resolves the equality deletes into row-position DVs and commits 
the data files together with the DVs to the target branch.
+
+The pipeline runs a single conversion cycle for every trigger event. 
Internally it is split into parallel stages (planner → reader → PK index → DV 
writer → committer), and it uses the same maintenance-framework lock as other 
tasks to guarantee mutual exclusion with concurrent maintenance operations 
(e.g. compaction) on the same table.
+
+```java
+.add(ConvertEqualityDeletes.builder()
+    .stagingBranch("staging")
+    .equalityFieldColumns(ImmutableList.of("id"))
+    .scheduleOnEqDeleteFileCount(10)
+    .parallelism(4))
+```
+
+Notes:
+
+**Prerequisites and staging-branch layout**
+
+- Table format version must be `>= 3` (the version that introduces deletion 
vectors). This is validated at job startup so a wrongly-configured job fails 
fast instead of silently doing nothing.
+- The staging branch is expected to be produced by the Flink `IcebergSink` in 
V3 mode: it may contain new data files, equality delete files, and deletion 
vectors, but **must not** contain V2 positional delete files. The planner 
throws if it encounters any V2 positional delete on the staging branch.

Review Comment:
   We don't require IcebergSink to produce the staging branch. I think we can 
remove that part of the note, e.g. "The staging branch needs to be written in 
V3 mode".



##########
docs/docs/flink-maintenance.md:
##########
@@ -82,6 +82,49 @@ Used to remove files which are not referenced in any 
metadata files of an Iceber
     .deleteBatchSize(1000))
 ```
 
+#### ConvertEqualityDeletes
+Converts equality delete files staged on a source branch into deletion vectors 
(DVs) on the target branch. Requires table format version >= 3 (which 
introduces DVs). This is typically used together with the Flink `IcebergSink`, 
which writes new data files and equality deletes to a staging branch; the 
converter then resolves the equality deletes into row-position DVs and commits 
the data files together with the DVs to the target branch.
+
+The pipeline runs a single conversion cycle for every trigger event. 
Internally it is split into parallel stages (planner → reader → PK index → DV 
writer → committer), and it uses the same maintenance-framework lock as other 
tasks to guarantee mutual exclusion with concurrent maintenance operations 
(e.g. compaction) on the same table.
+
+```java
+.add(ConvertEqualityDeletes.builder()
+    .stagingBranch("staging")
+    .equalityFieldColumns(ImmutableList.of("id"))
+    .scheduleOnEqDeleteFileCount(10)
+    .parallelism(4))
+```
+
+Notes:
+
+**Prerequisites and staging-branch layout**
+
+- Table format version must be `>= 3` (the version that introduces deletion 
vectors). This is validated at job startup so a wrongly-configured job fails 
fast instead of silently doing nothing.
+- The staging branch is expected to be produced by the Flink `IcebergSink` in 
V3 mode: it may contain new data files, equality delete files, and deletion 
vectors, but **must not** contain V2 positional delete files. The planner 
throws if it encounters any V2 positional delete on the staging branch.
+- The staging branch **must not remove data files**, i.e. do not run 
compaction / rewrite on the staging branch. If a staging snapshot removes data 
files, the cycle fails; run `RewriteDataFiles` on the target branch instead — 
the shared maintenance lock keeps it safe alongside this task.
+- `equalityFieldColumns` is required, must be non-empty, and must match the 
equality field columns the writer used to produce the staged equality delete 
files (see `IcebergSink.Builder#equalityFieldColumns`). Every staged equality 
delete file's `equalityFieldIds` must exactly equal this set; a mismatch fails 
the cycle. The partition source columns of every staged equality delete's spec 
must also be a subset of these equality columns.
+
+**Branch semantics**
+
+- When `stagingBranch` and `targetBranch` are different, the converter reads 
eq-deletes from the staging branch and commits the resolved data files and DVs 
to the target branch. The staging branch itself is not modified by this task.
+- When `stagingBranch == targetBranch`, the converter operates in-place: it 
acts as an equality-delete-to-DV compaction on that single branch. On cold 
start it walks the entire branch history so eq-deletes committed before the 
converter started are still picked up. Pure-insert snapshots on the shared 
branch are skipped without running a full cycle.

Review Comment:
   For consistency, I'd use either operators or natural language:
   ```suggestion
   - When `stagingBranch` and `targetBranch` are different, the converter reads 
eq-deletes from the staging branch and commits the resolved data files and DVs 
to the target branch. The staging branch itself is not modified by this task.
   - When `stagingBranch` and `targetBranch` are equal, the converter operates 
in-place: it acts as an equality-delete-to-DV compaction on that single branch. 
On cold start it walks the entire branch history so eq-deletes committed before 
the converter started are still picked up. Pure-insert snapshots on the shared 
branch are skipped without running a full cycle.
   ```



##########
docs/docs/flink-maintenance.md:
##########
@@ -239,60 +282,71 @@ env.execute("Table Maintenance Job");
 | `minAge(Duration)`                       | Remove orphan files created 
before this timestamp                                                           
                                                                                
                                                                                
                                                                                
            | 3 days ago              | Duration           |
 | `planningWorkerPoolSize(int)`            | Number of worker threads for 
planning snapshot expiration                                                    
                                                                                
                                                                                
                                                                                
           | Shared worker pool      | int                |
 
+#### ConvertEqualityDeletes Configuration
+
+| Method                               | Description                           
                                                                                
                                                                               
| Default Value | Type |
+|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|------|
+| `stagingBranch(String)`              | Source branch that holds the equality 
delete files and their associated data files. **Required.**                     
                                                                               
| None | String |
+| `targetBranch(String)`               | Target branch where converted data 
files and DVs are committed.                                                    
                                                                                
  | `main` | String |
+| `equalityFieldColumns(List<String>)` | Equality field column names used to 
build the primary key index. Must match the equality field columns used by the 
writer (see `IcebergSink.Builder#equalityFieldColumns`). **Required, 
non-empty.** | None | List&lt;String&gt; |
+
+!!! note
+`ConvertEqualityDeletes` requires table format version >= 3 (deletion vectors).

Review Comment:
   ```suggestion
       `ConvertEqualityDeletes` requires table format version >= 3 (deletion 
vectors).
   ```
   
   I believe this needs to be indented to be properly rendered.



##########
docs/docs/flink-maintenance.md:
##########
@@ -312,12 +366,12 @@ IcebergSink.forRowData(dataStream)
     .tableLoader(tableLoader)
     .rewriteDataFiles(Map.of(
         RewriteDataFilesConfig.MAX_BYTES, "1073741824"))
-    .expireSnapshots(Map.of(
-        ExpireSnapshotsConfig.RETAIN_LAST, "5",
-        ExpireSnapshotsConfig.MAX_SNAPSHOT_AGE_SECONDS, "604800"))
-    .deleteOrphanFiles(Map.of(
+        .expireSnapshots(Map.of(
+                                 ExpireSnapshotsConfig.RETAIN_LAST, "5",
+                         ExpireSnapshotsConfig.MAX_SNAPSHOT_AGE_SECONDS, 
"604800"))
+        .deleteOrphanFiles(Map.of(
         DeleteOrphanFilesConfig.MIN_AGE_SECONDS, "259200"))
-    .append();
+        .append();

Review Comment:
   Are these changes intended?



##########
docs/docs/flink-maintenance.md:
##########
@@ -388,25 +442,25 @@ Or specify options in table DDL:
 
 ```sql
 CREATE TABLE db.tbl (
-  ...
+    ...
 ) WITH (
-  'connector' = 'iceberg',
-  'catalog-name' = 'my_catalog',
-  'catalog-database' = 'db',
-  'catalog-table' = 'tbl',
-  'flink-maintenance.rewrite.enabled' = 'true',
-  'flink-maintenance.expire-snapshots.enabled' = 'true',
-  'flink-maintenance.delete-orphan-files.enabled' = 'true',
-
-  'flink-maintenance.rewrite.max-bytes' = '1073741824',
-  'flink-maintenance.expire-snapshots.retain-last' = '5',
-  'flink-maintenance.delete-orphan-files.min-age-seconds' = '259200',
-
-  'flink-maintenance.lock.type' = 'jdbc',
-  'flink-maintenance.lock.lock-id' = 'catalog.db.table',
-  'flink-maintenance.lock.jdbc.uri' = 
'jdbc:postgresql://localhost:5432/iceberg',
-  'flink-maintenance.lock.jdbc.init-lock-tables' = 'true'
-);
+      'connector' = 'iceberg',
+      'catalog-name' = 'my_catalog',
+      'catalog-database' = 'db',
+      'catalog-table' = 'tbl',
+      'flink-maintenance.rewrite.enabled' = 'true',
+      'flink-maintenance.expire-snapshots.enabled' = 'true',
+      'flink-maintenance.delete-orphan-files.enabled' = 'true',
+
+      'flink-maintenance.rewrite.max-bytes' = '1073741824',

Review Comment:
   Same here.



##########
docs/docs/flink-maintenance.md:
##########
@@ -82,6 +82,49 @@ Used to remove files which are not referenced in any 
metadata files of an Iceber
     .deleteBatchSize(1000))
 ```
 
+#### ConvertEqualityDeletes
+Converts equality delete files staged on a source branch into deletion vectors 
(DVs) on the target branch. Requires table format version >= 3 (which 
introduces DVs). This is typically used together with the Flink `IcebergSink`, 
which writes new data files and equality deletes to a staging branch; the 
converter then resolves the equality deletes into row-position DVs and commits 
the data files together with the DVs to the target branch.
+
+The pipeline runs a single conversion cycle for every trigger event. 
Internally it is split into parallel stages (planner → reader → PK index → DV 
writer → committer), and it uses the same maintenance-framework lock as other 
tasks to guarantee mutual exclusion with concurrent maintenance operations 
(e.g. compaction) on the same table.
+
+```java
+.add(ConvertEqualityDeletes.builder()
+    .stagingBranch("staging")
+    .equalityFieldColumns(ImmutableList.of("id"))
+    .scheduleOnEqDeleteFileCount(10)
+    .parallelism(4))
+```
+
+Notes:
+
+**Prerequisites and staging-branch layout**
+
+- Table format version must be `>= 3` (the version that introduces deletion 
vectors). This is validated at job startup so a wrongly-configured job fails 
fast instead of silently doing nothing.
+- The staging branch is expected to be produced by the Flink `IcebergSink` in 
V3 mode: it may contain new data files, equality delete files, and deletion 
vectors, but **must not** contain V2 positional delete files. The planner 
throws if it encounters any V2 positional delete on the staging branch.
+- The staging branch **must not remove data files**, i.e. do not run 
compaction / rewrite on the staging branch. If a staging snapshot removes data 
files, the cycle fails; run `RewriteDataFiles` on the target branch instead — 
the shared maintenance lock keeps it safe alongside this task.
+- `equalityFieldColumns` is required, must be non-empty, and must match the 
equality field columns the writer used to produce the staged equality delete 
files (see `IcebergSink.Builder#equalityFieldColumns`). Every staged equality 
delete file's `equalityFieldIds` must exactly equal this set; a mismatch fails 
the cycle. The partition source columns of every staged equality delete's spec 
must also be a subset of these equality columns.
+
+**Branch semantics**
+
+- When `stagingBranch` and `targetBranch` are different, the converter reads 
eq-deletes from the staging branch and commits the resolved data files and DVs 
to the target branch. The staging branch itself is not modified by this task.
+- When `stagingBranch == targetBranch`, the converter operates in-place: it 
acts as an equality-delete-to-DV compaction on that single branch. On cold 
start it walks the entire branch history so eq-deletes committed before the 
converter started are still picked up. Pure-insert snapshots on the shared 
branch are skipped without running a full cycle.
+- Only one **oldest unprocessed** staging snapshot is processed per trigger. 
If the writer produces staging snapshots faster than the converter can process 
them, they queue up on the staging branch. Configure `scheduleOnInterval` / 
`scheduleOnEqDeleteFileCount` / `scheduleOnCommitCount` (and 
`TableMaintenance.rateLimit`) accordingly to keep up with the ingestion rate.
+
+**State and restart**
+
+- The PK-index worker keeps a keyed row-position index of the target branch in 
Flink state and **maintains it incrementally across checkpoints**: each trigger 
cycle only applies the commits added on the target branch since the last 
indexed snapshot, and the resulting index is persisted with the next Flink 
checkpoint. On failover, the worker resumes from the most recent checkpointed 
index rather than rebuilding from scratch. Its size scales with the number of 
live rows in the table for the configured equality columns; size Flink state 
backend, checkpoint storage, and TaskManager memory accordingly.

Review Comment:
   Can we add a note that RocksDB is the prefered state backend due to its out 
of memory spilling?



##########
docs/docs/flink-maintenance.md:
##########
@@ -239,60 +282,71 @@ env.execute("Table Maintenance Job");
 | `minAge(Duration)`                       | Remove orphan files created 
before this timestamp                                                           
                                                                                
                                                                                
                                                                                
            | 3 days ago              | Duration           |
 | `planningWorkerPoolSize(int)`            | Number of worker threads for 
planning snapshot expiration                                                    
                                                                                
                                                                                
                                                                                
           | Shared worker pool      | int                |
 
+#### ConvertEqualityDeletes Configuration
+
+| Method                               | Description                           
                                                                                
                                                                               
| Default Value | Type |
+|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|------|
+| `stagingBranch(String)`              | Source branch that holds the equality 
delete files and their associated data files. **Required.**                     
                                                                               
| None | String |
+| `targetBranch(String)`               | Target branch where converted data 
files and DVs are committed.                                                    
                                                                                
  | `main` | String |
+| `equalityFieldColumns(List<String>)` | Equality field column names used to 
build the primary key index. Must match the equality field columns used by the 
writer (see `IcebergSink.Builder#equalityFieldColumns`). **Required, 
non-empty.** | None | List&lt;String&gt; |
+
+!!! note
+`ConvertEqualityDeletes` requires table format version >= 3 (deletion vectors).
+
 ### Complete Example
 
 ```java
 public class TableMaintenanceJob {
     public static void main(String[] args) throws Exception {
         StreamExecutionEnvironment env = 
StreamExecutionEnvironment.getExecutionEnvironment();
         env.enableCheckpointing(60000); // Enable checkpointing
-  
+
         // Configure table loader
         TableLoader tableLoader = TableLoader.fromCatalog(
-            CatalogLoader.hive("my_catalog", configuration),
-            TableIdentifier.of("database", "table")
+                CatalogLoader.hive("my_catalog", configuration),
+                TableIdentifier.of("database", "table")
         );
-  
+
         // Set up JDBC lock factory
         Map<String, String> jdbcProps = new HashMap<>();
         jdbcProps.put("jdbc.user", "flink");
         jdbcProps.put("jdbc.password", "flinkpw");
         jdbcProps.put("flink-maintenance.lock.jdbc.init-lock-tables", "true");
-  
+
         TriggerLockFactory lockFactory = new JdbcLockFactory(
-            "jdbc:postgresql://localhost:5432/iceberg",
-            "catalog.db.table",
-            jdbcProps
+                "jdbc:postgresql://localhost:5432/iceberg",
+                "catalog.db.table",
+                jdbcProps
         );
-  
+
         // Set up maintenance with comprehensive configuration
         TableMaintenance.forTable(env, tableLoader, lockFactory)
-            .uidSuffix("production-maintenance")
-            .rateLimit(Duration.ofMinutes(15))
-            .lockCheckDelay(Duration.ofSeconds(30))
-            .parallelism(4)
-  
-            // Daily snapshot cleanup
-            .add(ExpireSnapshots.builder()
-                .maxSnapshotAge(Duration.ofDays(7))
-                .retainLast(10))
-  
-            // Continuous file optimization
-            .add(RewriteDataFiles.builder()
-                .targetFileSizeBytes(256 * 1024 * 1024)
-                .minFileSizeBytes(32 * 1024 * 1024)
-                .scheduleOnDataFileCount(20)
-                .partialProgressEnabled(true)
-                .partialProgressMaxCommits(5)
-                .maxRewriteBytes(2L * 1024 * 1024 * 1024)
-                .parallelism(6))
-
-            // Delete orphans files created more than five days ago
-            .add(DeleteOrphanFiles.builder()
-                        .minAge(Duration.ofDays(5)))  
-  
-            .append();
-  
+                .uidSuffix("production-maintenance")
+                .rateLimit(Duration.ofMinutes(15))
+                .lockCheckDelay(Duration.ofSeconds(30))
+                .parallelism(4)
+
+                // Daily snapshot cleanup
+                .add(ExpireSnapshots.builder()
+                        .maxSnapshotAge(Duration.ofDays(7))
+                        .retainLast(10))
+
+                // Continuous file optimization
+                .add(RewriteDataFiles.builder()
+                        .targetFileSizeBytes(256 * 1024 * 1024)
+                        .minFileSizeBytes(32 * 1024 * 1024)

Review Comment:
   Do we need all these indent changes?



##########
docs/docs/flink-maintenance.md:
##########
@@ -526,7 +580,7 @@ These keys are used in SQL (SET or table WITH options) and 
are applicable when w
 **Recommendation:** Increase lock check delay and rate limit so that failed 
attempts back off and reduce contention.
 ```java
 .lockCheckDelay(Duration.ofMinutes(1)) // Wait longer before re-checking lock
-.rateLimit(Duration.ofMinutes(10))     // Reduce frequency of task execution
+        .rateLimit(Duration.ofMinutes(10))     // Reduce frequency of task 
execution

Review Comment:
   Same here (indention),



##########
docs/docs/flink-maintenance.md:
##########
@@ -82,6 +82,49 @@ Used to remove files which are not referenced in any 
metadata files of an Iceber
     .deleteBatchSize(1000))
 ```
 
+#### ConvertEqualityDeletes
+Converts equality delete files staged on a source branch into deletion vectors 
(DVs) on the target branch. Requires table format version >= 3 (which 
introduces DVs). This is typically used together with the Flink `IcebergSink`, 
which writes new data files and equality deletes to a staging branch; the 
converter then resolves the equality deletes into row-position DVs and commits 
the data files together with the DVs to the target branch.
+
+The pipeline runs a single conversion cycle for every trigger event. 
Internally it is split into parallel stages (planner → reader → PK index → DV 
writer → committer), and it uses the same maintenance-framework lock as other 
tasks to guarantee mutual exclusion with concurrent maintenance operations 
(e.g. compaction) on the same table.
+
+```java
+.add(ConvertEqualityDeletes.builder()
+    .stagingBranch("staging")
+    .equalityFieldColumns(ImmutableList.of("id"))
+    .scheduleOnEqDeleteFileCount(10)
+    .parallelism(4))
+```
+
+Notes:
+
+**Prerequisites and staging-branch layout**
+
+- Table format version must be `>= 3` (the version that introduces deletion 
vectors). This is validated at job startup so a wrongly-configured job fails 
fast instead of silently doing nothing.
+- The staging branch is expected to be produced by the Flink `IcebergSink` in 
V3 mode: it may contain new data files, equality delete files, and deletion 
vectors, but **must not** contain V2 positional delete files. The planner 
throws if it encounters any V2 positional delete on the staging branch.

Review Comment:
   Should we mention row lineage is not currently supported?



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