Skip to content

[fix](variant) Preserve projected shredded states across exchange - #66446

Open
Gabriel39 wants to merge 2 commits into
apache:branch-4.1from
Gabriel39:fix/accumulate-projected-shredded-variant-4.1
Open

[fix](variant) Preserve projected shredded states across exchange#66446
Gabriel39 wants to merge 2 commits into
apache:branch-4.1from
Gabriel39:fix/accumulate-projected-shredded-variant-4.1

Conversation

@Gabriel39

Copy link
Copy Markdown
Contributor

What changed

  • Preserve consecutive projected shredded Variant batches during indexed exchange gathers.
  • Keep the existing identical-schema append fast path and use an ordered segmented state only for incompatible partial schemas.
  • Merge homogeneous leaves as typed columns and normalize only heterogeneous physical leaf identities to logical Variant scalars.
  • Keep complete shredded states on the existing canonical validation path.

Root cause

An exchange channel can buffer selections from multiple scanner blocks. After the first projected shredded batch was adopted, the next indexed insertion forced the destination to encode. A leaf-only projection has no complete root metadata/value pair, so it cannot reconstruct the logical Variant. Different files can also shred the same path with incompatible physical types, which prevents the existing exact-schema append from handling the batch.

Impact

Projected Variant leaves can now cross exchange boundaries without premature root reconstruction. Non-Variant columns, complete Variant states, and compatible shredded schemas retain their existing paths.

Validation

  • VariantColumnReaderTest.*:ColumnVariantV2Test.*: 69 tests passed.
  • VariantElementV2EncodedTest.*:VariantElementV2TypedTest.*: 7 tests passed.
  • Added regression coverage for consecutive same-schema gathers, conflicting leaf types, reordering, and filtering.

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

@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
Gabriel39 marked this pull request as ready for review August 4, 2026 13:53
@Gabriel39
Gabriel39 requested a review from yiguolei as a code owner August 4, 2026 13:53
Gabriel39 added a commit to Gabriel39/incubator-doris that referenced this pull request Aug 4, 2026
### What problem does this PR solve?

Issue Number: None

Related PR: apache#66446

Problem Summary: Master exposes immutable subcolumn traversal through IColumn::ColumnCallback, while branch-4.1 still uses ImutableColumnCallback. Adapt the forward-port implementation to master’s callback contract so the composite shredded state overrides the interface and compiles.

### Release note

None

### Check List (For Author)

- Test: Unit Test
- Behavior changed: No
- Does this need documentation: No

@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.

Changes requested. I found three P1 correctness failures in the new cross-file/exchange state handling:

  1. Per-file projection can mix complete and incomplete shredded states, but the fallback only preserves incomplete/incomplete pairs and otherwise invokes the incomplete state's forbidden root materializer.
  2. Heterogeneous supported leaves are re-encoded without their Parquet primitive width, changing INT64/INT32 values to value-sized integers and low-precision decimals to 16-byte decimals.
  3. A projected shredded column preserved by an exchange gather has no valid remote wire representation: the Variant SerDe writes empty encoded buffers with a nonzero saved row count, and the receiver rejects it.

Critical checkpoint review:

  • Architecture/layering: the format-neutral VariantShreddedState boundary remains appropriately owned, but the new core composite can outlive the FileReader path without a corresponding serialization contract.
  • Lifecycle/selection: arbitrary/repeated selectors, range/filter slicing, empty segments, COW detachment, cache invalidation, and outer-null alignment were traced and no separate defect was found. The blocking lifecycle defect is the complete/incomplete transition described inline.
  • Schema/materialization/external compatibility: mapper eligibility, per-file footer finalization, residual fallback, Parquet annotations, integer widths, and decimal precision-derived widths were checked. The mixed-state and physical-identity regressions are both production-reachable.
  • Predicates/filtering/nulls: selection is applied consistently to nested values and outer null maps, and the element kernel merges outer nulls correctly. Missing paths and present residuals still trigger the mixed-state failure rather than a safe per-segment fallback.
  • Performance/observability/tests: profile-counter and bounded-copy paths did not produce another actionable regression. The added tests do not cover production mapper-eligible heterogeneous primitive IDs, mixed complete/incomplete files, or a multi-backend gather/SerDe boundary.
  • User focus: review_focus.txt contains no additional guidance, so the whole PR was reviewed without narrowing scope.
  • Process/completion: the requested repository-local code-review skill is absent in this checkout; the authoritative prompt, required be/src/format_v2/AGENTS.md, and its three mandatory design guides were applied instead. This was a static-only review as required, with no builds or source edits. Two full rounds converged with both normal scopes and the risk-focused scope returning NO_NEW_VALUABLE_FINDINGS against this final comment set.

_check_invariants();
return;
}
if (!_shredded->can_materialize() && !selected_source->can_materialize()) {

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.

[P1] Handle complete and incomplete file states together

Projection completeness is decided per file: the mapper keeps a full Variant when the key/type cannot be leaf-projected, and the Parquet finalizer also restores a full projection when residual all-null statistics are unavailable. A gather can therefore adopt an incomplete state from file A and then receive a complete state from file B. try_append() rejects the _complete mismatch, this condition is false, and the fallback calls ensure_encoded()/materialized_column() on the incomplete side, which throws instead of returning the requested rows (the reverse order fails too). The new binary test manually forces a partial binary plan, whereas the production mapper makes that file complete, so it misses this path. Please preserve mixed complete/incomplete segments with a per-segment direct/canonical fallback (including missing-path nulls), or make the projection decision uniform across files, and add production-path coverage.

nulls->insert_range_from(nullable.get_null_map_column(), 0, nullable.size());
continue;
}
auto typed = ColumnVariantV2::create_typed(match.column, match.type);

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.

[P1] Preserve the Parquet primitive width when normalizing mixed leaves

For two mapper-eligible partial files with different leaf types (for example INT64 7 and INT32 8), this heterogeneous branch wraps each match as a generic typed Variant and immediately encodes it. That encoder calls VariantScalarRef::integer(value) without the Parquet width, so both values become INT8; TYPE_DECIMAL128I is likewise always emitted as 16 bytes instead of the precision-derived Parquet width. The normal Parquet reconstruction path explicitly passes integer_width(schema, type) / decimal_width(...), and the existing baseline asserts that an INT64 leaf remains VariantPrimitiveId::INT64. Carry a schema-aware normalized value/physical identity into this branch and add a production-eligible heterogeneous test that asserts the primitive IDs, not only get_int().

return;
}
if (!_shredded->can_materialize() && !selected_source->can_materialize()) {
// Exchange channels can gather projected rows from files whose shredded leaf types

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.

[P1] Serialize a valid representation for remote exchange

This branch preserves a projected _shredded column inside the exchange channel's MutableBlock, but non-local channels immediately pass that block to the Variant binary SerDe. The SerDe only distinguishes typed from encoded state; for a shredded column it writes the still-empty metadata/id/value buffers while saving the nonzero _shredded->size(). Deserialization reconstructs zero rows and throws saved row count ... does not match decoded row count .... This is reachable with an outer element access above a subquery ORDER BY ... LIMIT: access-path collection still projects the leaf at the file scan, no project-pushdown rule moves the expression through LogicalTopN, and one-phase TopN requires a remote gather first. Please add an explicit shredded wire representation, materialize when complete, or fence incomplete projection from remote exchanges, and cover a multi-backend gather.

@hello-stephen

Copy link
Copy Markdown
Contributor

BE Regression && UT Coverage Report

Increment line coverage 62.24% (178/286) 🎉

Increment coverage report
Complete coverage report

Category Coverage
Function Coverage 73.64% (30341/41199)
Line Coverage 57.59% (333119/578464)
Region Coverage 54.40% (276896/508983)
Branch Coverage 55.41% (123979/223745)

@Gabriel39

Copy link
Copy Markdown
Contributor Author

run buildall

@Gabriel39

Copy link
Copy Markdown
Contributor Author

/review

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants