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

   ### Describe the problem
   
   Initializing the partitioned (non-global) record level index fails with 
`StackOverflowError` on tables with a few hundred data partitions or more.
   
   `PartitionedRecordIndexer.buildInitialization` returns one 
`DataPartitionAndRecords` per data partition, and 
`HoodieBackedTableMetadataWriter.initializeMetadataPartition` 
(`HoodieBackedTableMetadataWriter.java:467-472`) unions them one at a time:
   
   ```java
   HoodieData<HoodieRecord> records = engineContext.emptyHoodieData();
   for (DataPartitionAndRecords dataPartitionAndRecords: 
initializationPlan.dataPartitionAndRecords()) {
     initializeFileGroups(...);
     records = records.union(dataPartitionAndRecords.indexRecords());
   }
   bulkCommit(instantTimeForPartition, relativePartitionPath, records, 
initializationPlan.indexParser());
   ```
   
   `HoodieJavaRDD.union` is `JavaRDD.union`, and Spark does not flatten nested 
unions, so the lineage passed to `bulkCommit` is a chain of nested `UnionRDD`s 
as deep as the number of data partitions. Spark Java-serializes that lineage 
into the task binary recursively, once per level, so the driver overflows in 
`DAGScheduler.submitMissingTasks` (`Task serialization failed: 
java.lang.StackOverflowError`, surfacing as `HoodieMetadataException: Bootstrap 
on record_index partition failed`). When driver serialization succeeds, 
executors can overflow while deserializing the task instead, which kills the 
executor.
   
   The loop has been there since partitioned RLI was added in #13603 (release 
1.1.0); the indexer refactor in #18348 kept it.
   
   Measured on JDK 11 (arm64), Spark 3.5, local mode, one file per partition:
   
   | `-Xss` | passes up to | fails from |
   |---|---|---|
   | 512k | 250 partitions | 260-300 |
   | 1m (Linux x86_64 default) | ~450-520 | ~500-550 |
   | 2m (macOS arm64 default) | ~1000 | 1100+ |
   
   So with the usual 1 MB stack, roughly 500 data partitions (about 18 months 
of daily partitions) is enough to break initialization.
   
   ### Repro
   
   Write a COW table with N partitions (one record each) with RLI off, then 
enable `hoodie.metadata.record.level.index.enable=true` so the next metadata 
writer initializes the partitioned RLI. With N around 600 on a 1 MB stack the 
initialization fails as above. A deterministic check is to register a 
`SparkListener` during initialization and compute the longest chain of 
`UnionRDD`s from the stage `rddInfos`: it equals N (50 for N = 50).
   
   ### Proposed fix
   
   Collect the `indexRecords()` into a list in the loop and union them once 
with a flat union (`SparkContext.union(Seq)` yields a single `UnionRDD`). There 
is no `HoodieEngineContext.union(List)` today, so this needs a small 
engine-context API (with a fold fallback for non-Spark engines), plus a 
regression test asserting the union depth stays flat.
   


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