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

   ### Bug Description
   
   ## Bug Description
   
   **What happened:**
   
   On a COPY_ON_WRITE table written by the Flink sink with `write.operation = 
insert_overwrite`, rows whose serialized size substantially exceeds 
`write.memory.segment.page.size` (32 KB default) are read back from the write 
buffer corrupted. The failure surfaces during Avro conversion as an 
`ArrayIndexOutOfBoundsException` with a nonsensical segment index:
   
   ```
   java.lang.ArrayIndexOutOfBoundsException: Index 51817 out of bounds for 
length 8
        at 
org.apache.flink.table.data.binary.BinarySegmentUtils.getByteMultiSegments(BinarySegmentUtils.java:601)
        at 
org.apache.flink.table.data.binary.NestedRowData.isNullAt(NestedRowData.java:199)
        at 
org.apache.hudi.util.RowDataToAvroConverters$13.convert(RowDataToAvroConverters.java:316)
   ```
   
   The rows in question are ~230 KB, so at the default page size each spans 8 
memory segments — which matches the `length 8` in the message. The reported 
index (51817) implies a nested-row offset of roughly 1.7 GB, i.e. the packed 
`offsetAndSize` for the nested `ROW` field was read from the wrong place.
   
   **What you expected:**
   
   Rows larger than a single memory-segment page should round-trip through the 
write buffer intact, or the write should fail with a clear error naming the 
limit.
   
   **Steps to reproduce:**
   
   1. COW table, Flink sink via `HoodiePipeline`, `write.operation = 
insert_overwrite`.
   2. Schema containing a nested `ROW` field, at least one member of which is a 
large `STRING` (a JSON blob of ~200 KB per row in our case), so that the 
serialized `BinaryRowData` is comfortably larger than one page.
   3. Leave `write.memory.segment.page.size` at its 32 KB default.
   4. Write a few million such rows.
   
   I do **not** have a self-contained reproducer yet — see the caveat at the 
end.
   
   ## Analysis
   
   `StreamWriteFunction` buffers rows per bucket in a `RowDataBucket`, which 
wraps a Flink `BinaryInMemorySortBuffer` built by `BufferUtils.createBuffer` 
over a `MemorySegmentPool` whose page size comes from 
`write.memory.segment.page.size`:
   
   - `StreamWriteFunction.doBufferRecord` → `RowDataBucket.writeRow` → 
`BinaryInMemorySortBuffer.write`, which calls 
`RowDataSerializer.serializeToPages`.
   - On flush, `RowDataBucket.getDataIterator` → 
`BinaryInMemorySortBuffer.getIterator` → `getRecordFromBuffer` → 
`BinaryRowDataSerializer.mapFromPages` → `pointTo`.
   
   `pointTo` takes a single-segment fast path when the row fits in the 
remainder of the current page, and otherwise goes through 
`pointToMultiSegments`, which assembles a `MemorySegment[]` for the row. With 
32 KB pages and ~230 KB rows, **every** row takes the multi-segment path.
   
   Reading the nested `ROW` field then goes `BinaryRowData.getRow` → 
`BinarySegmentUtils.readRowData`, which unpacks a `long` from the fixed-length 
part into `(offset, size)` and calls `NestedRowData.pointTo(segments, offset + 
baseOffset, size)`. The observed ~1.7 GB offset means that `long` is not the 
value that was written, so the parent row's fixed-length part is being 
addressed incorrectly once the row is spread across segments.
   
   I read `BinaryRowDataSerializer.pointToMultiSegments` looking for an 
off-by-one in the array sizing and did not find one — it allocates `varSegSize 
+ 1` segments computed from `remainLength`, which does account for the row 
starting mid-segment. So I have localized the failure but not root-caused it.
   
   **Scope:** this only affects operations that route through 
`StreamWriteFunction` — `upsert`, `insert_overwrite`, `insert_overwrite_table`. 
Plain `insert` is unaffected, because `OptionsResolver.isAppendMode` sends it 
to `Pipelines.append`, whose `AppendWriteFunction` hands each row straight to 
`BulkInsertWriterHelper` and never touches this buffer. We confirmed that 
empirically: the identical data and schema write fine under `insert`.
   
   **Partial mitigations that do not hold:** raising 
`write.memory.segment.page.size` reduces how often rows straddle a page but 
does not eliminate it, because `checkSkipWriteForFixLengthPart` only guarantees 
the row's *fixed* part fits the current page — the variable-length part can 
still cross the boundary. Our actual workaround was to project the large column 
out of the write schema so rows fit in a single page.
   
   ## Caveat
   
   This is a report from a production-shaped workload, not from a minimal 
reproducer, and I have not yet confirmed it against a released artifact — only 
against a build from `release-1.2` in which the classes above are unmodified. 
Happy to put together a standalone test (COW + `insert_overwrite` + a nested 
`ROW` with a multi-hundred-KB `STRING` member at the default page size) if that 
would help triage.
   
   ### Environment
   
   - **Hudi version:** 1.2.x, built from `release-1.2`. The classes in the 
stack are unmodified from upstream.
   - **Flink version:** 1.18 (`hudi-flink1.18-bundle`), BATCH execution mode, 
bounded source
   - **Table type:** COPY_ON_WRITE
   - **Storage:** object store (GCS-backed Hadoop FS)
   - **Running on Docker?** no (YARN)
   
   Relevant options — everything not listed is at its default:
   
   ```
   write.operation                              = insert_overwrite
   table.type                                   = COPY_ON_WRITE
   hoodie.datasource.write.recordkey.field      = <a top-level STRING column>
   hoodie.datasource.write.partitionpath.field  = <a top-level DOUBLE column>
   write.tasks                                  = 32
   hoodie.metadata.enable                       = false
   write.memory.segment.page.size               = 32768   (default)
   write.task.max.size                          = 1024    (default)
   ```
   
   
   ### Logs and Stack Trace
   
   ```
   org.apache.hudi.exception.HoodieUpsertException: Failed to upsert for commit 
time <instant>
        at 
org.apache.hudi.table.action.commit.FlinkWriteHelper.write(FlinkWriteHelper.java:81)
        at 
org.apache.hudi.table.action.commit.FlinkInsertOverwriteCommitActionExecutor.execute(FlinkInsertOverwriteCommitActionExecutor.java:71)
        at 
org.apache.hudi.table.HoodieFlinkCopyOnWriteTable.insertOverwrite(HoodieFlinkCopyOnWriteTable.java:241)
        at 
org.apache.hudi.client.HoodieFlinkWriteClient.insertOverwrite(HoodieFlinkWriteClient.java:298)
        at 
org.apache.hudi.sink.StreamWriteFunction$WriteFunction.write(StreamWriteFunction.java:516)
        at 
org.apache.hudi.sink.StreamWriteFunction.writeRecords(StreamWriteFunction.java:445)
        at 
org.apache.hudi.sink.StreamWriteFunction.flushBucket(StreamWriteFunction.java:381)
        at 
org.apache.hudi.sink.StreamWriteFunction.bufferRecord(StreamWriteFunction.java:323)
        at 
org.apache.hudi.sink.StreamWriteFunction.processElement(StreamWriteFunction.java:184)
        ...
   Caused by: org.apache.hudi.exception.HoodieException: operation has failed
        at 
org.apache.hudi.common.util.queue.BoundedInMemoryQueue.throwExceptionIfFailed(BoundedInMemoryQueue.java:274)
        at 
org.apache.hudi.common.util.queue.BoundedInMemoryQueue.insertRecord(BoundedInMemoryQueue.java:202)
        at 
org.apache.hudi.common.util.queue.IteratorBasedQueueProducer.produce(IteratorBasedQueueProducer.java:45)
        ...
   Caused by: org.apache.hudi.exception.HoodieException: Index 51817 out of 
bounds for length 8
        at 
org.apache.hudi.io.BaseCreateHandle.doWrite(BaseCreateHandle.java:123)
        at 
org.apache.hudi.io.HoodieWriteHandle.write(HoodieWriteHandle.java:241)
        at 
org.apache.hudi.execution.ExplicitWriteHandler.consume(ExplicitWriteHandler.java:48)
        ...
   Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 51817 out of 
bounds for length 8
        at 
org.apache.flink.table.data.binary.BinarySegmentUtils.getByteMultiSegments(BinarySegmentUtils.java:601)
        at 
org.apache.flink.table.data.binary.BinarySegmentUtils.getByte(BinarySegmentUtils.java:593)
        at 
org.apache.flink.table.data.binary.BinarySegmentUtils.bitGet(BinarySegmentUtils.java:536)
        at 
org.apache.flink.table.data.binary.NestedRowData.isNullAt(NestedRowData.java:199)
        at 
org.apache.flink.table.data.RowData.lambda$createFieldGetter$1(RowData.java:293)
        at 
org.apache.hudi.util.RowDataToAvroConverters$13.convert(RowDataToAvroConverters.java:316)
        at 
org.apache.hudi.util.RowDataToAvroConverters$12.convert(RowDataToAvroConverters.java:284)
        at 
org.apache.hudi.util.RowDataToAvroConverters$13.convert(RowDataToAvroConverters.java:315)
        at 
org.apache.hudi.util.RowDataToAvroConverters$12.convert(RowDataToAvroConverters.java:284)
        at 
org.apache.hudi.client.model.HoodieFlinkRecord.toIndexedRecord(HoodieFlinkRecord.java:286)
        at 
org.apache.hudi.io.storage.HoodieAvroFileWriter.writeWithMetadata(HoodieAvroFileWriter.java:44)
        at 
org.apache.hudi.io.BaseCreateHandle.writeRecordToFile(BaseCreateHandle.java:165)
        at 
org.apache.hudi.io.BaseCreateHandle.doWrite(BaseCreateHandle.java:103)
   ```


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