Support DST timezones conversion for ORC [databricks] - #14544
Conversation
Signed-off-by: Chong Gao <res_life@163.com>
Signed-off-by: Chong Gao <res_life@163.com>
Signed-off-by: Chong Gao <res_life@163.com>
Greptile SummaryThis PR adds proper DST-aware timezone conversion for ORC reads on GPU. It replaces the prior
Confidence Score: 5/5Safe to merge; the core timezone rebasing logic, resource lifecycle, and coalescing split checks are all correct. The change is well-structured: timezone resolution uses ZoneId.of with SHORT_IDS (avoiding the silent-GMT fallback of TimeZone.getTimeZone), resource management in rebaseWithWriterTimezone uses withResource/safeMap correctly, the chunked and non-chunked table producer paths both call the rebase step, and the coalescing reader correctly splits batches on timezone-rule mismatches. The only finding is that the STRUCT branch in rebaseNestedWithWriterTimezone always allocates a new ColumnView even when no child changed — unlike the LIST branch which short-circuits — causing an unnecessary copyToColumnVector for STRUCT columns without timestamp descendants. This is a performance nit, not a data-correctness regression. Files Needing Attention: GpuOrcTimezoneUtils.scala — the STRUCT short-circuit optimisation in rebaseNestedWithWriterTimezone. Important Files Changed
Sequence DiagramsequenceDiagram
participant S as ORC Stripe Footer
participant B as buildOutputStripes
participant P as MakeOrcTableProducer
participant U as GpuOrcTimezoneUtils
participant J as GpuTimeZoneDB (JNI)
S->>B: getWriterTimezone() per stripe
B->>B: resolveWriterTimezone(id) via ZoneId.SHORT_IDS
B->>B: writerTimezonesShareRules(distinctTzs)
B-->>P: writerTimezone: ZoneId
P->>U: rebaseOrcTimestamps(table, writerTimezone)
U->>J: buildOrcTimezoneContext(writerTz, readerTz)
J-->>U: OrcTimezoneContext
U->>J: convertOrcTimezones(col, tzCtx) per timestamp col
J-->>U: rebased ColumnVector
U-->>P: rebased Table (input closed)
P->>P: evolveSchemaIfNeededAndClose(rebased, ...)
Reviews (11): Last reviewed commit: "Allow expected non-UTC ORC write fallbac..." | Re-trigger Greptile |
Signed-off-by: Chong Gao <res_life@163.com>
|
Tip: Greploops — Automatically fix all review issues by running Use the Greptile plugin for Claude Code to query reviews, search comments, and manage custom context directly from your terminal. |
|
Can you address the comments from greptile first ? |
Replace scala.util.Random.nextLong() with a fixed seed (42L) so the ORC timezone matrix tests are reproducible. A non-deterministic seed risks intermittent failures if a generated timestamp happens to land near a DST boundary that exposes a latent bug. Signed-off-by: Chong Gao <chongg@nvidia.com> Signed-off-by: Chong Gao <res_life@163.com>
Replace the hardcoded "/tmp/tmp_OrcTimezonePerfSuite" path with a per-run path under java.io.tmpdir suffixed with a UUID, so concurrent runs on the same host (e.g., CI matrix) cannot corrupt each other's data. Signed-off-by: Chong Gao <chongg@nvidia.com> Signed-off-by: Chong Gao <res_life@163.com>
ORC footers can contain legacy/short timezone IDs like "PST", "CST", or "ACT". On JDK 21 ZoneId.of(...) rejects these, while java.util.TimeZone.getTimeZone(...) still accepts them, so the cross-TZ GPU path could fail on files the CPU path reads cleanly. Route the writer timezone through TimeZone.getTimeZone(...).toZoneId once at the entry point so downstream code (the same-rules check, the JNI kernel, and the writer base-offset computation) all see a canonical ZoneId ID consistent with the java.util.TimeZone semantics ORC uses. Signed-off-by: Chong Gao <chongg@nvidia.com> Signed-off-by: Chong Gao <res_life@163.com>
buildOutputStripes was using raw string equality on the per-stripe writer timezone, so a file whose stripes carry semantically equivalent but differently spelled IDs (e.g. "US/Pacific" vs "America/Los_Angeles", "UTC" vs "GMT", or "" alongside an explicit JVM-default ID) would be rejected with an IOException even though the CPU path reads it fine. Group the collected IDs through java.util.TimeZone.hasSameRules and only fail when the underlying rules actually differ. When all stripes agree, prefer the first non-empty ID so downstream code keeps an explicit timezone string. Signed-off-by: Chong Gao <chongg@nvidia.com> Signed-off-by: Chong Gao <res_life@163.com>
The matrix only exercised canonical region IDs, so the new ORC read path's behavior around legacy/alias timezone strings (e.g. "PST", "US/Pacific") was untested. java.util.TimeZone accepts these and they can show up in real ORC footers, but ZoneId.of rejects them on JDK 21. Add "US/Pacific" (alias of "America/Los_Angeles") and "PST" (legacy short ID) to the writer/reader matrix so the alias-equivalence path in the stripe-timezone check and the legacy-ID normalization in the cross-TZ rebase are both covered. Signed-off-by: Chong Gao <chongg@nvidia.com> Signed-off-by: Chong Gao <res_life@163.com>
The wildcard `import org.apache.spark.sql._` already pulls the project's own `org.apache.spark.sql.FileUtils` object into scope (the same pattern used by `TimeZonePerfSuite`), but greptile's static analysis flags the call as an unresolved symbol. Add an explicit import so the dependency on `FileUtils.deleteRecursively` is obvious to readers and tools. Signed-off-by: Chong Gao <chongg@nvidia.com>
firestarman
left a comment
There was a problem hiding this comment.
One low-priority nit from my pass.
Signed-off-by: Chong Gao <chongg@nvidia.com>
|
build |
Signed-off-by: Chong Gao <chongg@nvidia.com>
Signed-off-by: Chong Gao <chongg@nvidia.com>
Signed-off-by: Chong Gao <chongg@nvidia.com>
Signed-off-by: Chong Gao <chongg@nvidia.com>
This is a cross-implementation ORC interoperability issue, not data corruption in libcudf itselfTracked in rapidsai/cudf#23422. I reproduced it end-to-end with a real ReproductionA
Root causelibcudf writes the column as a plain ORC The round-trip contract of a plain Why the other paths are fine (round-trip safe)
DirectionThe libcudf ORC output is self-consistent and valid for the ORC C++ reader, but it is not round-trip-safe against the ORC Java reader that Spark/Hive use in a non-UTC session. Two ways to fix:
|
Signed-off-by: Chong Gao <chongg@nvidia.com>
|
build |
|
lgtm |
revans2
left a comment
There was a problem hiding this comment.
This looks good as a patch for reading ORC timestamps, but I am really concerned about rapidsai/cudf#23422 and I am not sure that cudf takes the local into account when writing. From what the AI tells me it does not look at the local at all. So that means we are either handing the wrong things to CUDF when we write, or our read code is not interpreting what we handed to cudf correctly. I don't think this is a cudf bug, but I could be wrong. But it is a blocker for any release that we have. This could be the difference between a TIMESTAMP and a TIMESTAMP_INSTANT being written out to the file. I think we need to dig deeper before we say that cudf is wrong here.
| @tz_sensitive_test | ||
| @pytest.mark.xfail( | ||
| is_not_utc(), | ||
| reason="https://github.com/rapidsai/cudf/issues/23422") |
There was a problem hiding this comment.
Have you validated that this is indeed a cudf issue? To me this is a blocker P0 issue and reading/writing timestamps in ORC for anything but UTC should probably be disabled until we can fix this.
There was a problem hiding this comment.
Confirmed it's a libcudf writer bug, and agreed it's a blocker.
Root cause: ORC's plain timestamp is timezone-agnostic — on read the value is shifted by convertBetweenTimezones(writerTimezone, readerTimezone), so correctness depends entirely on the writerTimezone stored in each StripeFooter. The Spark CPU writer stamps the actual JVM zone (e.g. Asia/Shanghai), so a same-zone read cancels out. libcudf hardcodes writerTimezone = "UTC" (cpp/src/io/orc/writer_impl.cu:2674) and exposes no writer-timezone option at any layer (C++ orc_writer_options, cuDF Java ORCWriterOptions, spark-rapids-jni). So a GPU file written in a non-UTC JVM carries writerTimezone="UTC" and is read back shifted by the zone offset (−8h for Asia/Shanghai, matching the repro). It's not a plugin data bug — we hand cudf the correct UTC instants; cudf just can't record the real writer zone.
On the TIMESTAMP vs TIMESTAMP_INSTANT point: exactly — the file is only correct under UTC-reader/instant semantics, but Spark reads it as a local timestamp. Fixing this needs two coupled changes inside libcudf (stamp the real zone and base the seconds on that zone's 2015 epoch); setting the footer field alone still reads wrong, which is why it can't be worked around via existing options. Tracked in rapidsai/cudf#23422.
For this PR: the read path is fixed here and covered by the timezone matrix; only GPU write in non-UTC is affected. I'll restore the write-side CPU fallback for non-UTC timestamp ORC writes (instead of the xfail) so we never emit CPU-incompatible files until the cudf fix lands.
Signed-off-by: Chong Gao <chongg@nvidia.com>
|
build |
cuDF's ORC writer always stamps writerTimezone="UTC" in the stripe footer and cannot record the JVM writer timezone (rapidsai/cudf#23422). Because ORC's timestamp type is timezone-agnostic, a GPU-written file in a non-UTC JVM is read back shifted by the zone offset by a CPU ORC reader. Restore the write-side guard so non-UTC timestamp ORC writes fall back to CPU, and update the test to assert the fallback instead of xfail. Signed-off-by: Chong Gao <chongg@nvidia.com>
|
build |
Signed-off-by: Chong Gao <chongg@nvidia.com>
|
build |
1 similar comment
|
build |
|
@revans2 Dug into this as you asked. You're right on both counts — and the result changes the ask rather than the diagnosis.
It's the first one, and the mechanism is narrower than my issue originally claimed. libcudf and the Spark CPU writer store byte-identical values. The only thing that differs is one metadata string in the stripe footer. With writer zone
libcudf hardcodes
Right axis — and it's exactly why I am not proposing we switch the type. Spark writes plain
Agreed that cudf is not miscomputing anything — its own reader and liborc both round-trip its files correctly under any One more thing that came out of this, which I think settles it. Before this PR, the old path was gated by
So GPU→GPU was only ever correct when So where this leaves us:
That fallback is a pure perf loss caused by one unsettable field, which is the concrete cost to point at. I've rewritten rapidsai/cudf#23422 accordingly — retitled to Does that address the blocker, or would you rather hold the PR until the cudf side lands? |
Fixes #13437.
Depends on:
Description
Context
ORC OSS uses java.util.TimeZone to do rebase, it does not use java.time.ZoneId API.
The java.util.TimeZone and java.time.ZoneId have inconsistent behavior.
cuDF have
java.timecompatible impl, but does not havejava.utilcompatible impljava.util.TimeZone.getOffsetandjava.time.ZoneId.getOffsetare not always consistent.For more details, click to expand
Solution 1 [not feasible], use cuDF with ignoreTimezoneInStripeFooter=False.
cuDF manages the ORC writer timezone decode.
Problem: for far-future timestamps projected into the synthetic 400-year cycle, dates before the first synthetic DST transition were incorrectly using the first cycle entry, which is the DST offset. That causes exactly the +1 hour winter drift you saw for America/Los_Angeles with years like 8770.
cuDF has
java.timecompatible impl instead ofjava.util. We can not get correct result.So this solution is not feasible.
Solution 2, develope kernel, use cuDF with ignoreTimezoneInStripeFooter=True
cuDF does not manage the ORC writer timezone decode. Decode as UTC in cuDF.
All time rebasing logic is handled by customized JNI kernel which is compatible to
java.util.changes
Why: A pre-built timezone info file can not handle for all Java versions, in future the timezone info may change.
java.util.TimezonelogicRelated cuDF issue
perf number
How to run, refer to `OrcTimezonePerfSuite.scala`, click to expand
Checklists
Documentation
Testing
(Please provide the names of the existing tests in the PR description.)
Performance
Signed-off-by: Chong Gao chongg@nvidia.com