Skip to content

[opt](parquet) Fuse fragmented nullable selection planning - #66397

Open
Gabriel39 wants to merge 1 commit into
apache:branch-4.1from
Gabriel39:dev/parquet-v2-fused-nullable-selection-4.1
Open

[opt](parquet) Fuse fragmented nullable selection planning#66397
Gabriel39 wants to merge 1 commit into
apache:branch-4.1from
Gabriel39:dev/parquet-v2-fused-nullable-selection-4.1

Conversation

@Gabriel39

@Gabriel39 Gabriel39 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

What

  • Fuse nullable definition-level runs with the row filter in one traversal.
  • Produce physical decode ranges, the selected NULL map, and selected value counts without first materializing and rescanning a row-wise selection map.
  • Reuse the existing selected-decoder strategies and nullable in-place expansion.
  • Restrict fusion to batches with at least 1,024 rows, at least 10% NULLs, and materially fragmented definition-level runs. No-NULL, low-NULL, clustered, nested, and non-expandable shapes keep the legacy path.

Why the guard is conservative

The full benchmark matrix includes no-NULL, low-NULL, and clustered level plans as negative controls. Those shapes do not remove enough legacy work to guarantee a win, so this change deliberately leaves them unchanged. Decoder selection and encoding-specific materialization are not modified.

Verification

  • ASAN: NativeNullableSelectionTest.* and benchmark scenario tests: 16/16 passed.
  • ASAN: ParquetV2NativeDecoderTest.*: 118/118 passed.

Release microbenchmark

Coverage Legacy/fused pairs Correctness Regressions Mean CPU change Least improvement
Full scenario matrix 100 Identical ranges and NULL maps N/A (includes negative controls) N/A N/A
Production-eligible scenarios 30 Identical ranges and NULL maps 0 -42.68% 8.08%
Scenario Repetitions Legacy median CPU Fused median CPU CPU change Legacy CV Fused CV
10% selectivity / 50% NULL, fragmented 10 400,220 ns 177,905 ns -55.55% 0.58% 1.28%
99% selectivity / 50% NULL, high-selectivity boundary 10 687,385 ns 460,657 ns -32.98% 0.60% 1.32%

The microbenchmark isolates nullable selection planning; it is not presented as an end-to-end query speedup.

@hello-stephen

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Apache Doris.
Don't know what should be done next? See How to process your PR.

Please clearly describe your PR:

  1. What problem was fixed (it's best to include specific error reporting information). How it was fixed.
  2. Which behaviors were modified. What was the previous behavior, what is it now, why was it modified, and what possible impacts might there be.
  3. What features were added. Why was this function added?
  4. Which code was refactored and why was this part of the code refactored?
  5. Which functions were optimized and what is the difference before and after the optimization?

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@Gabriel39
Gabriel39 marked this pull request as ready for review August 3, 2026 15:07
@Gabriel39
Gabriel39 requested a review from yiguolei as a code owner August 3, 2026 15:07

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Two changes are required before this is ready:

  1. Add a reader-level regression that actually enters the new fused nullable-selection branch and verifies a subsequent batch/page, rather than testing only the planner helper and eligibility predicate.
  2. Make the benchmark guide's validation record consistent with the newly registered 292 ParquetKernel cases.

Critical checkpoints:

  • Correctness and cursor state: the logical-to-physical selection mapping matched the legacy contract in independent exhaustive checks, including zero-length run parity, nonzero filter offsets, selected/filtered NULL and non-NULL rows, and long-run splitting. No concrete value-shift or row-count defect was found.
  • Decoder, error, and lifecycle behavior: the fused and legacy paths enter the same prepared decoder/materialization logic; supported plain, dictionary, boolean, byte-stream-split, and delta paths consume the same physical selection. Conversion-failure remapping, rollback behavior, page continuation, and _remaining_num_values updates were traced without finding a distinct defect.
  • Performance and observability: the gate remains limited to large, materially nullable, fragmented flat batches; nested and non-expandable shapes retain the legacy path. The new scratch is included in reserve/release accounting and the existing hybrid batch/range counters remain attributable.
  • Tests and documentation: helper-level oracle coverage is broad, but the production wiring is not exercised; the validation-record count is also stale. These are the two inline requests above.
  • Compatibility and resource safety: no new format-compatibility, ownership, unbounded-memory, or corruption-path issue was substantiated.
  • User focus: no additional user-provided focus was supplied, so the whole PR was reviewed normally.

Review status: complete after two rounds. The final normal production, benchmark/coverage, and separate risk-focused scans all returned NO_NEW_VALUABLE_FINDINGS against this frozen comment set and unchanged head a67a328803bc805059d02d52a5eb7bdc7af69dee.

Review constraint: no repository code-review SKILL.md exists in this checkout, and the review contract prohibited local builds, so this was a static review supplemented by independent planner models and the submitted test/benchmark evidence.

EXPECT_EQ(filter, nullptr);
}

TEST(NativeNullableSelectionTest, BuildsPhysicalRangesAndSelectedNullsInOnePass) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new tests exercise build_filtered_nullable_selection() and the eligibility predicate directly, but none drives the actual ScalarColumnReader::_read_values() branch into materialize_fused_nullable_values(). That leaves the new wiring unprotected: a regression in nonzero _filter_map_index, page/_remaining_num_values advancement, or nullable expansion could pass all of these tests. Please add a reader-level case with at least 1,024 fragmented nullable rows and a partial filter, assert the values and NULL layout, then read a subsequent batch/page so the production path and cursor continuation are covered.


be/output/lib/benchmark_test --benchmark_list_tests \
| grep -c '^ParquetKernel/' # currently 92
| grep -c '^ParquetKernel/' # currently 292

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update the Current validation record below as part of this count change. It still says 92 kernel, while this command and the matrix description now correctly expect 292 (92 existing plus 200 nullable-selection cases). The guide currently gives reviewers contradictory pass/fail criteria.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 65.78% (123/187) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 73.56% (30201/41054)
Line Coverage 57.45% (330558/575393)
Region Coverage 54.32% (275152/506543)
Branch Coverage 55.28% (122638/221843)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants