Repository navigation
[Iceberg] Merge on read fixes - #40678
ahmedabu98 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
📋 Review Summary
This PR adds constant value maps (idToConstant) for partition identity columns and v3 row-lineage metadata (_row_id and _last_updated_sequence_number) to ReadUtils and wires them through ScanTaskReader and CdcReadUtils. It also fixes BeamBaseIncrementalChangelogScan to restore firstRowId from manifest entries when planning changelog tasks.
🔍 General Feedback
- The core row-lineage and partition constant wiring is clean and well-tested across Parquet, Avro, and ORC.
- However, there are a few edge cases around row-lineage synthesis on deleted rows, row-metadata ordering in
CdcOutputUtils, andScanTaskReaderpartition constants that should be addressed.
💡 Tip: Comment /gemini review on this pull request to trigger a new Gemini code review.
💬 Inline Findings
sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/cdc/CdcReadUtils.java(line 225): 🟠 High:CdcOutputUtils.outputRowshort-circuits and returnsdataAndRowMetadataunchanged whenmetadataColumnscontains only row metadata columns (_row_id,_last_updated_sequence_number):if (metadataColumns.isEmpty() || metadataColumns.stream().allMatch(IcebergCdcMetadataColumns::isRowMetadataColumn)) { return dataAndRowMetadata; }.
When a user configures withMetadataColumns(ImmutableList.of("_last_updated_sequence_number", "_row_id")) (without any commit/change metadata columns), outputSchema has fields [..., _last_updated_sequence_number, _row_id]. However:
- In the unidirectional path (
applyColumnPruning = true),DeleteReader.fileProjectionappends missing_posafter the requested columns, andCdcReadUtilsline 229 then appends_row_idafter_pos(or after_last_updated_sequence_numbereven if_row_idwas already requested first), producing aRecordwhose schema does not matchprojectedBeamRowSchemaoroutputBeamRowSchema. - In the bidirectional path (
applyColumnPruning = false) or when_last_updated_sequence_numberis requested before_row_id,CdcOutputUtils.outputRowreturnsrowdirectly with schema[..., _last_updated_sequence_number, _row_id](fromprojectedBeamRowSchema), or ifCdcReadUtilsreorders them,StructProjection/icebergRecordToBeamRowmay mismatch withoutputSchema.
Ensure that when _row_id is added solely as an internal dependency for computing _last_updated_sequence_number, it is stripped out when projecting to projectedBeamRowSchema / outputBeamRowSchema, and that CdcOutputUtils.outputRow preserves user-configured column ordering even when only row metadata columns are requested.
sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/cdc/CdcReadUtils.java(line 236): 🟠 High: When reading deleted rows (DELETED_ROWSorDELETED_FILE) with_row_idor_last_updated_sequence_numberrequested,deleteReaderfilters the baseDataFilerecords beforeRowLineage.synthesizeRowLineagewraps theCloseableIterableat line 255.
Because DeleteReader wraps the underlying reader iterable and filters out non-deleted rows, SynthesizeRowLineageIterator only sees the surviving (deleted) records and increments rowPosition as 0, 1, 2, ... for each emitted deleted record rather than using the record's actual physical row position (_pos) in the data file. As a result, for any row deleted at physical position p > 0 (via position or equality deletes), _row_id will be synthesized as firstRowId + emittedIndex instead of firstRowId + _pos.
Either apply RowLineage.synthesizeRowLineage to records before passing records to deleteReader.read(records) (for the non-Parquet path), or read _pos from the record when available instead of using an invocation counter.
-
sdks/java/io/iceberg/src/main/java/org/apache/beam/sdk/io/iceberg/ScanTaskReader.java(line 117): 🟡 Medium:ReadUtils.idToConstant(table, currentTask, project)populates partition identity constants (PartitionUtil.constantsMap(task, ...)). However,ScanTaskReaderpassesidToConstantonly to the Parquet reader builder (.reuseContainers().withNameMapping(...).idToConstant(idToConstant)) and does not pass.constants(idToConstant)toORC.read(...)orAvro.read(...). Additionally,ScanTaskReaderstill manually overwrites partition columns after reading inadvance()(lines 169-175), whereasRowLineage.synthesizeRowLineageis not applied for Avro/ORC inScanTaskReaderif row-lineage columns are ever projected there. Passing.constants(idToConstant)to ORC and Avro builders keeps format handling consistent withCdcReadUtils. -
sdks/java/io/iceberg/src/main/java/org/apache/iceberg/BeamBaseIncrementalChangelogScan.java(line 175): 🟡 Medium:restoreFirstRowIdusesDataFiles.builder(entry.file().spec()).copy(entry.file()).withFirstRowId(entry.file().firstRowId()).build()whenentry.file().firstRowId() != null. Note thatDataFiles.Builder.copy(DataFile)in Iceberg does not copydataSequenceNumberorfileSequenceNumber(which are set onBaseFileby the manifest reader), so rebuilding theDataFileviaDataFiles.builder(...).copy(...)will resetdataSequenceNumber()andfileSequenceNumber()tonull(or-1), breaking_last_updated_sequence_numbersynthesis inReadUtils.idToConstant(task.file().dataSequenceNumber()).
Please verify whether DataFiles.Builder.copy preserves dataSequenceNumber and fileSequenceNumber in the Iceberg version used here; if entry.file() already has firstRowId() != null, why does entry.file() need to be rebuilt at all, or if copy() drops firstRowId elsewhere in ManifestGroup, ensure dataSequenceNumber is preserved.
|
Assigning reviewers: R: @chamikaramj for label iceberg. Note: If you would like to opt out of this review, comment Available commands:
The PR bot will only process comments in the main thread (not review comments). |
|
AI review comment is hallucinating.. there's no such |
Fixed some bugs in the batch and CDC sources:
keep: [id]andfilter: "amount > 10")id = 123andkeep: [amount]).top.nested = 'xyz'withkeep: [id]used to return every row as deleted)