Omit Parquet min/max statistics for float/double columns containing NaN - #22818
Conversation
A float/double column with a NaN mixed among non-NaN values was getting column-chunk min/max statistics computed from the non-NaN values: the min/max reduce uses cuda::std::min/max and a NaN never wins an IEEE comparison, so it is silently dropped. Per PARQUET-1246 such a column must omit min/max, otherwise a reader doing predicate pushdown (e.g. col = NaN) uses the bogus bounds to skip the row group and drops valid rows. Track whether a NaN was seen in the float/double chunk reduce, propagate it through block_reduce and the chunk merge, and force has_minmax = false for Parquet when it is set. ORC is unchanged (the guard is PARQUET-only). Covers any float/double leaf, including nested in list/struct/map. Found via NVIDIA/cudf-spark#15004. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Allen Xu <allxu@nvidia.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis PR adds ChangesNaN Detection in Parquet Statistics
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cpp/src/io/statistics/typed_statistics_chunk.cuh`:
- Around line 171-173: The device code in the typed_statistics_chunk
specialization incorrectly calls isnan(elem); update the check to use
cuda::std::isnan(elem) so the device-safe overload is used (replace isnan with
cuda::std::isnan in the block that sets has_nan when elem is NaN), mirroring the
fix applied in the typed_statistics_chunk<T, true> specialization and ensuring
the device compilation uses the CUDA std isnan implementation.
In `@cpp/tests/io/parquet_writer_test.cpp`:
- Around line 856-895: The test FloatingPointWithNaNStatsOmitted currently
checks basic NaN handling but misses nulls, multi-row-group, and
nested-list/struct edge cases; extend the ParquetWriterTest by adding sub-tests
that (1) create a column_wrapper<float> with mixed NaN and null validity (e.g.,
col_with_nulls) and assert get_statistics(...) on its column omits min/max, (2)
write the same data using
parquet_writer_options::builder(...).row_group_size_rows(2) to force multiple
row groups and verify for each fmd.row_groups[i].columns[...] that
get_statistics(...) reports no min/max, and (3) construct a LIST and a STRUCT
column whose leaf float/double child contains NaN values (use make_lists_column
/ struct construction used elsewhere in tests) and assert get_statistics(...) on
the parent columns also omits min/max; reuse read_footer, write_parquet,
get_statistics, and the existing expected/table_view setup to locate where to
add these cases.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 16cec354-bd55-4911-8a64-c27294bff6ed
📒 Files selected for processing (4)
cpp/src/io/statistics/column_statistics.cuhcpp/src/io/statistics/statistics.cuhcpp/src/io/statistics/typed_statistics_chunk.cuhcpp/tests/io/parquet_writer_test.cpp
Fixes the pre-commit.ci clang-format failure (trailing-comment alignment). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Allen Xu <allxu@nvidia.com>
There was a problem hiding this comment.
Pull request overview
This PR fixes Parquet writer behavior for floating-point columns that contain NaN values by ensuring min/max statistics are omitted when any NaN is present, matching the Parquet ecosystem convention (PARQUET-1246) and preventing incorrect predicate pushdown row-group skipping.
Changes:
- Track
has_nanduring per-chunk statistics reduction for floating-point types and propagate it through block reduction and chunk merges. - For Parquet only, force
has_minmax = falsewhenhas_nanis set so min/max are not written. - Add a Parquet writer regression test covering mixed-NaN and all-NaN float/double columns.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
cpp/src/io/statistics/typed_statistics_chunk.cuh |
Adds has_nan tracking during element reduction, propagates it through block reduction, and carries it into the untyped statistics_chunk. |
cpp/src/io/statistics/statistics.cuh |
Extends statistics_chunk with a has_nan flag to persist NaN presence across kernels/merges. |
cpp/src/io/statistics/column_statistics.cuh |
For Parquet merges, disables min/max emission when any contributing chunk observed a NaN. |
cpp/tests/io/parquet_writer_test.cpp |
Adds a regression test asserting min/max are omitted for float/double columns containing NaN, while unaffected for no-NaN control. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…ested list Addresses review feedback on NVIDIA#22818. Extends FloatingPointWithNaNStatsOmitted with a column that mixes a NaN and a null (null handling must not mask NaN detection), and adds two tests: FloatingPointWithNaNStatsOmittedAcrossFragments (a single NaN in a middle page fragment must propagate through the fragment -> column-chunk statistics merge so the chunk still omits min/max) and FloatingPointWithNaNStatsOmittedNested (a NaN in a float leaf of a LIST column omits min/max). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Signed-off-by: Allen Xu <allxu@nvidia.com>
Signed-off-by: Allen Xu <allxu@nvidia.com>
Signed-off-by: Allen Xu <allxu@nvidia.com>
|
/ok to test 35230a3 |
Signed-off-by: Allen Xu <allxu@nvidia.com>
|
/ok to test 37df470 |
|
/ok to merge |
|
/ok to test |
@wjxiz1992, there was an error processing your request: See the following link for more information: https://docs.gha-runners.nvidia.com/cpr/e/1/ |
|
/ok to test 0f21ea3 |
|
/merge |
…aN (#22818) Closes #22817. A `float`/`double` column containing a NaN mixed with non-NaN values was getting column-chunk min/max statistics computed from the non-NaN values only. The min/max reduce in `cpp/src/io/statistics/typed_statistics_chunk.cuh` uses `cuda::std::min/max` over `extrema_type::convert(val)` (which returns the value unchanged), and since `NaN < x` / `NaN > x` are both false a NaN never wins the reduce and is silently dropped — so the chunk ends up with a defined min/max that ignores the NaN. Per the Parquet convention adopted in parquet-mr (PARQUET-1246), a floating-point column that contains any NaN must omit min/max; otherwise a reader doing predicate pushdown (e.g. `col = NaN`, or a range predicate) uses the bogus bounds to skip the row group and silently drops valid rows. An all-NaN column was safe only incidentally — the merge's `has_minmax = (minimum_value <= maximum_value)` check flips it off because the reduce leaves the accumulator at inverted identity — but the mixed NaN + non-NaN case passes that check. Found via NVIDIA/cudf-spark#15004 (a GPU-written file read back by CPU Spark with a `= NaN` predicate-pushdown filter returned 0 rows instead of the matching NaN row). Affects any float/double leaf, top-level or nested in list/struct/map at any depth. ### Fix Track whether a NaN was seen during the float/double chunk reduce (`has_nan`), propagate it through `block_reduce` and the chunk merge, and force `has_minmax = false` for Parquet when it is set. ORC is unchanged: the guard is `if constexpr (IO == PARQUET)`, and the flag is only carried (never acted on) for ORC. Added `ParquetWriterTest.FloatingPointWithNaNStatsOmitted` (float / double / all-NaN must omit min/max; a no-NaN control still writes them). `ParquetWriterTest` (54 tests) and the `*Stats*` / `*ColumnIndex*` suites (45 tests) pass locally. Authors: - Allen Xu (https://github.com/wjxiz1992) Approvers: - Muhammad Haseeb (https://github.com/mhaseeb123) URL: #22818
Description
Closes #22817.
A
float/doublecolumn containing a NaN mixed with non-NaN values was getting column-chunk min/max statistics computed from the non-NaN values only. The min/max reduce incpp/src/io/statistics/typed_statistics_chunk.cuhusescuda::std::min/maxoverextrema_type::convert(val)(which returns the value unchanged), and sinceNaN < x/NaN > xare both false a NaN never wins the reduce and is silently dropped — so the chunk ends up with a defined min/max that ignores the NaN. Per the Parquet convention adopted in parquet-mr (PARQUET-1246), a floating-point column that contains any NaN must omit min/max; otherwise a reader doing predicate pushdown (e.g.col = NaN, or a range predicate) uses the bogus bounds to skip the row group and silently drops valid rows. An all-NaN column was safe only incidentally — the merge'shas_minmax = (minimum_value <= maximum_value)check flips it off because the reduce leaves the accumulator at inverted identity — but the mixed NaN + non-NaN case passes that check.Found via NVIDIA/cudf-spark#15004 (a GPU-written file read back by CPU Spark with a
= NaNpredicate-pushdown filter returned 0 rows instead of the matching NaN row). Affects any float/double leaf, top-level or nested in list/struct/map at any depth.Fix
Track whether a NaN was seen during the float/double chunk reduce (
has_nan), propagate it throughblock_reduceand the chunk merge, and forcehas_minmax = falsefor Parquet when it is set. ORC is unchanged: the guard isif constexpr (IO == PARQUET), and the flag is only carried (never acted on) for ORC.Added
ParquetWriterTest.FloatingPointWithNaNStatsOmitted(float / double / all-NaN must omit min/max; a no-NaN control still writes them).ParquetWriterTest(54 tests) and the*Stats*/*ColumnIndex*suites (45 tests) pass locally.Checklist