heshshark opened a new issue, #19763:
URL: https://github.com/apache/hudi/issues/19763

   ### Bug Description
   
   **What happened:**
   
   We can reproducibly trigger silent data loss in the Flink COW write path 
after restoring from an unaligned checkpoint. The problem is not limited to 
insert clustering: we reproduced it with `write.operation=insert` and 
`write.insert.cluster=true`, and independently observed the same physical 
overwrite pattern with `write.operation=upsert`.
   
   An in-flight record tagged as INSERT (`instantTime = "I"`) is restored from 
checkpoint channel state and reaches the writer before UPDATE records for the 
same `partitionPath + fileId`. The writer bucket therefore starts with an 
INSERT record even though the file group already has a committed base file.
   
   The write path selects `FlinkCreateHandle` for the existing fileId instead 
of a merge/concat handle. A new parquet file is written with the same fileId 
but only the small set of records from the current batch. Snapshot reads select 
this newer base-file version and hide the records that remain physically 
present in the older parquet file.
   
   There is no exception and the commit succeeds.
   
   Observed evidence from a deterministic reproduction:
   
   - Existing file group at instant A contained 82,182 records.
   - After recovery, the next writer bucket contained 2,028 records:
     - 57 records tagged INSERT
     - 1,971 records tagged UPDATE
     - first record type was INSERT
   - Hudi selected `FlinkCreateHandle`.
   - No previous base-file path was opened by the handle.
   - The next commit metadata reported `prevCommit = null` and `numWrites = 
numInserts`.
   - The new parquet reused the existing fileId but did not carry forward the 
old base-file records.
   - Snapshot row count decreased although `numDeletes = 0` and there were no 
write errors.
   
   The same pattern was independently observed on a COW table configured with 
`write.operation=upsert`. Three existing file groups were replaced as follows:
   
   - 408 previous rows -> 3 rows in the new base file
   - 569 previous rows -> 2 rows in the new base file
   - 448 previous rows -> 3 rows in the new base file
   
   For all three new versions, commit metadata reported `prevCommit = null`, 
`numWrites = numInserts`, `numUpdates = 0`, and no deletes or write errors. 
Direct physical parquet reads confirmed that the old files still contained the 
hidden records.
   
   Therefore, `write.insert.cluster` increases exposure in the insert path but 
is not a necessary condition for the underlying handle-selection failure.
   
   **What you expected:**
   
   Restoring records from a valid Flink checkpoint must not cause an existing 
Hudi file group to be treated as a new file handle in either INSERT or UPSERT 
mode.
   
   For a fileId that already has a committed base file, Hudi should either:
   
   1. select the merge/concat handle and preserve the existing base-file 
records; or
   2. fail fast when a writer bucket contains inconsistent INSERT/UPDATE tags 
for the same existing file group.
   
   A successful commit with zero deletes must not reduce the visible records of 
an existing file group.
   
   **Steps to reproduce:**
   
   Scenario A — deterministic insert-path reproduction:
   
   1. Create a COPY_ON_WRITE table with a primary key.
   2. Configure:
      - `write.operation = insert`
      - `write.insert.cluster = true`
      - unaligned checkpoints enabled
   3. Write enough data to create and commit a base file for a fileId.
   4. Arrange for an INSERT-tagged record for that fileId to remain in channel 
state when an unaligned checkpoint completes.
   5. Restart the job from that checkpoint.
   6. After recovery, send UPDATE-tagged records routed to the same 
`partitionPath + fileId`, with the restored INSERT record arriving first.
   7. Let the next Hudi instant commit.
   8. Compare the old/new parquet files, commit metadata, and snapshot row 
count.
   
   Scenario B — upsert path:
   
   1. Create a COPY_ON_WRITE table with a primary key and `write.operation = 
upsert`.
   2. Enable unaligned checkpoints; `write.insert.cluster` is not required.
   3. Create and commit existing file groups.
   4. Restore from a checkpoint that contains in-flight INSERT-tagged records.
   5. Allow restored INSERT-tagged and current UPDATE-tagged records for the 
same fileId to enter the next writer bucket.
   6. Inspect the next commit for an existing fileId with `prevCommit = null` 
and `numWrites = numInserts`.
   7. Physically compare the old and new parquet files for that fileId.
   
   The issue becomes easier to reproduce with Flink mini-batch enabled because 
more records are released together after recovery.
   
   **Likely code path:**
   
   In Hudi 0.15.x, `StreamWriteFunction` groups records by `partitionPath + 
fileId`. Handle selection ultimately depends on the first record passed to 
`FlinkWriteHandleFactory`:
   
   ```java
   if (loc.getInstantTime().equals("I")) {
     writeHandle = new FlinkCreateHandle<>(...);
   } else {
     writeHandle = createMergeHandle(...);
   }
   ```
   
   This makes handle selection dependent on restored record ordering instead of 
whether the file group already has a committed base file. The condition is 
shared by the COW commit write path used by both insert and upsert.
   
   **Workaround:**
   
   Disabling unaligned checkpoints prevents the reproduction in our tests:
   
   ```
   execution.checkpointing.unaligned.enabled = false
   ```
   
   This is only a workaround. Unaligned checkpoints are a supported Flink 
exactly-once feature, and Hudi should safely handle restored in-flight records.
   
   
   ### Environment
   
   **Hudi version:** 0.15.0
   
   **Query engine:** Flink 1.18.1 / Flink SQL
   
   **Table type:** COPY_ON_WRITE
   
   **Storage:** S3-compatible object storage
   
   **Write mode:** Streaming
   
   **Reproduced/observed write configurations:**
   
   Insert path:
   
   ```properties
   write.operation=insert
   write.insert.cluster=true
   write.precombine=false
   execution.checkpointing.unaligned.enabled=true
   table.exec.mini-batch.enabled=true
   table.exec.mini-batch.size=5000
   table.exec.mini-batch.allow-latency=5s
   ```
   
   Upsert path:
   
   ```properties
   write.operation=upsert
   write.precombine=false
   execution.checkpointing.unaligned.enabled=true
   ```
   
   The mini-batch settings and `write.insert.cluster` increase reproduction 
probability in the insert scenario, but neither is required for the upsert 
observation.
   
   
   ### Logs and Stack Trace
   
   No exception is thrown. The job and Hudi commit both complete successfully.
   
   Deterministic insert-path diagnostic sequence:
   
   ```text
   instant A:
     fileId=<same-file-id>
     baseFileRows=82182
   
   restored writer bucket for instant B:
     records=2028
     insertTagged=57
     updateTagged=1971
     firstRecordType=I
   
   handle selection:
     handleClass=FlinkCreateHandle
     previousWritePath=NONE
   
   commit metadata for instant B:
     fileId=<same-file-id>
     prevCommit=null
     numWrites=2028
     numInserts=2028
     numUpdates=0
     numDeletes=0
     totalWriteErrors=0
   ```
   
   Independent upsert-path observations:
   
   ```text
   file group A: previousWrites=408, currentWrites=3, currentPrevCommit=null
   file group B: previousWrites=569, currentWrites=2, currentPrevCommit=null
   file group C: previousWrites=448, currentWrites=3, currentPrevCommit=null
   
   all current versions:
     numWrites=numInserts
     numUpdates=0
     numDeletes=0
     totalWriteErrors=0
   ```
   
   Physical validation:
   
   ```text
   old parquet: same fileId, hidden records are present
   new parquet: same fileId, hidden records are absent
   snapshot: selects new parquet, so old records are no longer visible
   ```
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to