sumitsingh-in opened a new issue, #20182:
URL: https://github.com/apache/hudi/issues/20182
### Bug Description
**What happened:**
`HoodieFileGroupReaderBasedFileFormat` decides whether to build a
`VectorizedParquetRecordReader` from a mutable field, `supportVectorizedRead`,
that is written only inside `supportBatch`. Spark does not call `supportBatch`
for scans whose schema exceeds `spark.sql.codegen.maxFields` (default 100,
counting nested leaves), so a wide nested scan reuses whatever the last narrow
query on the same relation left in that field. `buildReaderWithPartitionValues`
also writes that stale value into the session conf
`spark.sql.parquet.enableVectorizedReader`, which Spark reads in
`FileSourceScanExec.needsUnsafeRowConversion`.
Two user-visible consequences:
1. A query whose plan says `Batched: false`, on a nested schema, with
`spark.sql.parquet.enableNestedColumnVectorizedReader=false`, is read with
`VectorizedParquetRecordReader` anyway. Whether that happens depends on which
statement ran earlier in the session.
2. When the session conf reads `false` at execute time (left there by an
earlier wide scan on another Hudi table, after `REFRESH TABLE`, or set by the
user) while the stale field is `true`, Spark skips the `UnsafeRow` conversion,
the reader emits `ColumnarBatchRow`, and `collect()` fails:
**What you expected:**
- The reader type is decided from the schema of the scan being built, as
`ParquetFileFormat.buildReaderWithPartitionValues` does with
`ParquetUtils.isBatchReadSupportedForSchema(sqlConf, resultSchema)` and as Hudi
0.14's `Spark33LegacyHoodieParquetFileFormat` did with
`resultSchema.forall(_.dataType.isInstanceOf[AtomicType])`. A nested schema
with `enableNestedColumnVectorizedReader=false` is never handed to the
vectorized reader, regardless of what ran earlier in the session.
- Hudi does not mutate `spark.sql.parquet.enableVectorizedReader` on the
session.
- No `ClassCastException`.
-
**Steps to reproduce:**
Spark 3.3, default `spark_catalog` (Hive Metastore or in-memory). Do **not**
set `spark.sql.catalog.spark_catalog=HoodieCatalog` (see "Why Hudi's own SQL
suite does not catch it" above).
```
spark-shell \
--conf spark.serializer=org.apache.spark.serializer.KryoSerializer \
--conf
spark.sql.extensions=org.apache.spark.sql.hudi.HoodieSparkSessionExtension \
--conf spark.sql.parquet.enableVectorizedReader=true \
--conf spark.sql.parquet.enableNestedColumnVectorizedReader=false \
--jars hudi-spark3.3-bundle_2.12-<version>.jar
```
1. Create a COW table whose nested leaf count exceeds
`spark.sql.codegen.maxFields` (100) and insert two rows:
```scala
val fields = (1 to 128).map(i => s"f$i int").mkString(", ")
spark.sql(s"create table t (id int, ts long, name string, payload
struct<$fields>) using hudi " +
s"tblproperties (primaryKey = 'id', preCombineField = 'ts')
location '/tmp/hudi_t'")
val ns = (1 to 128).map(i => s"'f$i', $i").mkString(", ")
spark.sql(s"insert into t select 1, 1L, 'a', named_struct($ns)")
spark.sql(s"insert into t select 2, 1L, 'b', named_struct($ns)")
```
2. Run a narrow query. Required schema is empty, `supportBatch` runs and
stores `supportVectorizedRead = true`:
```scala
spark.sql("select count(*) from t").show()
spark.conf.get("spark.sql.parquet.enableVectorizedReader") // "true"
```
3. Run the wide nested query. `isTooManyFields` is true, `supportBatch` is
skipped, the stale `true` is reused:
```scala
spark.sql("select * from t").explain() // Scan HudiFileGroup ...
Batched: false
spark.sql("select * from t").collect() // succeeds, but is read with
VectorizedParquetRecordReader
```
4. Run the same query with the conf at `false` when Spark evaluates
`needsUnsafeRowConversion`. The explicit `set` stands in for an earlier wide
scan on another Hudi table, or on this table after `REFRESH TABLE`, both of
which write `"false"` at L349:
```scala
spark.conf.set("spark.sql.parquet.enableVectorizedReader", "false")
spark.sql("select * from t").collect()
// java.lang.ClassCastException: class
org.apache.spark.sql.vectorized.ColumnarBatchRow cannot be cast to
// class org.apache.spark.sql.catalyst.expressions.UnsafeRow
```
5. Order control: in a fresh session run step 3 before step 2. Both queries
pass and `spark.conf.get("spark.sql.parquet.enableVectorizedReader")` reads
`"false"` afterwards, because the wide scan wrote the stored `false` into the
session.
To watch the decision being skipped, enable DEBUG for
`org.apache.spark.sql.execution.datasources.parquet.HoodieFileGroupReaderBasedFileFormat`:
exactly one `supportReturningBatch: ... supportVectorizedRead: true` line
appears, for `count(*)`, and none for `select *`.
A ScalaTest that reproduces this deterministically is attached and can go
into the PR. It pulls the file format from the analyzed plan and checks
identity with `eq`, reads the stored field by reflection, checks
`FileSourceScanExec.supportsColumnar`, checks the conf after each statement,
runs the reverse-order control, and includes the `ClassCastException` step.
Observed values on release-1.2:
| Step | Check | Actual |
|---|---|---|
| same relation | file format object for `count(*)` eq file format object
for `select *` / relation present in `SessionCatalog.tableRelationCache` |
`true` / `true` |
| fresh | `supportVectorizedRead` before any execution | `false` |
| guard | `WholeStageCodegenExec.isTooManyFields` for `select *` (128 nested
fields, limit 100) | `true` |
| after `count(*)` | `supportVectorizedRead` / session conf | `true` /
`"true"` |
| `select *` | `FileSourceScanExec.supportsColumnar` | `false` |
| after `select *` | `supportVectorizedRead` / session conf | `true` /
`"true"` (succeeded only because Spark still converted rows) |
| conf set `"false"`, then `select *` | | `ClassCastException` |
### Environment
**Hudi version:** master
**Query engine:** (Spark/Flink/Trino etc)
Spark
**Relevant configs:**
### Logs and Stack Trace
_No response_
--
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]