Conversation
| def ignoreMissingIds(conf: SQLConf): Boolean = | ||
| conf.getConfString(IGNORE_MISSING_PARQUET_FIELD_ID, "false").toBoolean | ||
|
|
||
| def requiresDatetimeRebase( |
There was a problem hiding this comment.
|
Thanks for this @peterxcli — the diagnosis is right, and the Spark 2.4.5 "no My concern is where the check runs. I opened #5202 as an alternative that does the same detection in the native Parquet reader factory, where the footer has already been fetched and cached for the read itself — so it costs no additional I/O. Being at that point also lets it consult row-group statistics, which matters because Spark stamps The tradeoff is the outcome: yours falls back to Spark and always returns correct results, mine raises. I don't think fallback is reachable without paying the plan-time cost, but that's worth other opinions. One thing #5202 should take from here regardless: you read the rebase modes from |
… conf Spark resolves the rebase modes through `ParquetOptions`, so `.option( "datetimeRebaseMode", "CORRECTED")` on the reader overrides the session conf. Comet read `SQLConf` directly and ignored the option, so a user who scoped the setting to one read rather than the session would still have the scan refused. Read them from `ParquetOptions` instead -- the same source Spark's own `ParquetFileFormat` uses, which falls back to the session conf when the option is absent. Covered for every version-less fixture, since those are the files the read modes apply to. Spotted by @peterxcli in the review of apache#5048.
Trading a silent wrong answer for a fallback is the right call, and I like that the fallback decision mirrors Spark's own Three concerns. Every Parquet footer is now read on the driver at planning time
Some things that would help:
What happens when the write rebase mode is The native writer now unconditionally stamps Does The val hasTimestamp =
SupportLevel.containsType(scanExec.requiredSchema, classOf[TimestampType]) ||
(COMET_SCHEMA_EVOLUTION_ENABLED &&
SupportLevel.containsType(scanExec.requiredSchema, classOf[TimestampNTZType]))Tying the NTZ case to schema evolution is not obvious. Is the reasoning that NTZ can only come from an INT96 physical column when schema evolution lets a One smaller thing The version comparison |
- Cache per-file footer facts (keyed by path, length, modification time) so AQE re-planning and repeated queries do not re-read footers - Add spark.comet.scan.parquet.checkDatetimeRebase config to opt out of the planning-time footer check - Fall back to Spark for native Parquet writes when the write rebase mode is LEGACY and the schema contains dates or timestamps - Gate the TimestampNTZ rebase check on COMET_ALLOW_TIMESTAMP_LTZ_AS_NTZ instead of COMET_SCHEMA_EVOLUTION_ENABLED, with an explanation - Document that the version comparison mirrors Spark's DataSourceUtils.datetimeRebaseSpec Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…tetime-rebase # Conflicts: # native/core/src/execution/operators/parquet_writer.rs # native/core/src/execution/planner.rs # native/proto/src/proto/operator.proto # spark/src/test/scala/org/apache/comet/parquet/CometParquetWriterSuite.scala # spark/src/test/scala/org/apache/comet/parquet/ParquetReadSuite.scala
|
Thanks @andygrove — all four points were worth acting on. Addressed in 2323bae (and the branch is now merged up to main, which also resolves the conflict with #5490's 1. Plan-time footer reads The earlier round had already moved the check to
On the pre-DPP file set (from your earlier comment): 2. LEGACY write rebase mode Correct — there was no check, and the write path silently ignored Deliberately not gated: EXCEPTION, the default. Spark under EXCEPTION writes the same corrected bytes as CORRECTED and only raises when it encounters a pre-Gregorian value; falling back on EXCEPTION would disable native datetime writes under default configs entirely, and the raise is data-dependent so it can't be detected at plan time. The remaining divergence is narrow — Comet writes a correct, correctly-stamped value where Spark would raise on an ancient one. I can file a follow-up for a native-side ancient-value check if you want EXCEPTION parity. 3. The TimestampNTZ condition Your guess is exactly right, and the constant I used made it look worse than it is. TIMESTAMP_NTZ never needs rebasing by itself: Spark neither rebases NTZ on write ( 4. The version comparison Added a comment noting that Tests: the three |
andygrove
left a comment
There was a problem hiding this comment.
Thanks for working through the last round. SKIP_ROW_GROUPS, the bounded pool with the early exit, the per-file cache and the opt-out cover what I asked about the planning cost, and gating LEGACY writes plus stamping org.apache.spark.version closes the write-side hole.
The branch conflicts with main now, and one of the changes it needs to absorb won't show up as a conflict. #5763 moved native writes on Spark 4.0+ to CometWriteFiles and CometWriteFilesExec, so after the rebase the LEGACY rebase-mode gate and setSparkVersion in CometDataWritingCommand only cover 3.x. Without setSparkVersion, the 4.x path stamps org.apache.spark.version with an empty string. Spark compares that against "3.0.0" as a string and treats the file as legacy, so it rebases Comet-written ancient dates and timestamps on read, and this PR's scan check falls back on every Comet-written file. Could the gate and the version go into a helper both serdes use, and could ParquetWriterExec leave the key out when the version is empty?
The other thing is the read check on Spark 3.4 and 3.5, where datetimeRebaseModeInRead and int96RebaseModeInRead default to EXCEPTION. A file without org.apache.spark.version falls through to the mode, so every Parquet file written by something other than Spark, such as Trino, Hive, Flink, pyarrow or DuckDB, now falls back whenever the query reads a date or timestamp. That's the right call for matching Spark, since under EXCEPTION Spark raises on an ancient value we would return. But the version stamp this PR adds to createParquetWriter in CometTestBase makes every helper-written fixture look Spark-written, so no test exercises it, and scans.md doesn't mention it. Could scans.md spell out the 3.x case and point those users at setting the two Spark read modes to CORRECTED, the 4.0 default, rather than at checkDatetimeRebase=false? And could a test write a file without the version key and pin both outcomes, falling back under EXCEPTION and staying native under CORRECTED?
Which issue does this PR close?
Closes #5010.
Rationale for this change
Native Parquet scans cannot currently apply Spark's legacy Julian/Gregorian rebasing safely before Parquet pruning, filters, and aggregates consume datetime values. This can produce incorrect results for files written by Spark 2.x or with legacy datetime or INT96 rebasing.
I want to merge this conservative fallback first to restore correctness with a small, low-risk change. The native implementation in #5047 should remain a follow-up and can restore native acceleration after its broader per-file rebasing behavior is reviewed.
What changes are included in this PR?
SparkParquetOptions.use_legacy_date_timestamp_or_ntzfield.spark.comet.scan.parquet.checkDatetimeRebase(defaulttrue) to skip the check for data known to be corrected.LEGACYand the schema contains dates or timestamps, since the native writer always writes corrected values and stamps corrected metadata.Planning-time cost
The footer check reads each input file's footer (with
SKIP_ROW_GROUPS) on the driver the first time the file is planned, 8 files at a time, stopping at the first legacy footer. Measured with 2,000 single-row files on a local SSD: ~10.9s cold (~5.4ms/file), 16-57ms once cached. Object storage is latency-bound (roughly 1-2 footer GETs per file across 8 threads); the per-file cache and the opt-out config exist for that case.How are these changes tested?
make coregit diff --check./mvnw test -Dtest=none -Dsuites="org.apache.comet.parquet.ParquetReadV1Suite fallback for Parquet datetime rebasing" -Dscalastyle.skip=true./mvnw test -Dtest=none -Dsuites="org.apache.comet.parquet.CometParquetWriterSuite" -Dscalastyle.skip=true(34/34, including the new LEGACY write-mode fallback test)cargo test -p datafusion-comet parquet_writer