Skip to content

feat(merge): legacy promotion path + body-col schema evolution#6423

Open
g-talbot wants to merge 1 commit into
gtt/streaming-merge-engine-multi-rg-3-hardeningfrom
gtt/legacy-promotion-and-schema-evo
Open

feat(merge): legacy promotion path + body-col schema evolution#6423
g-talbot wants to merge 1 commit into
gtt/streaming-merge-engine-multi-rg-3-hardeningfrom
gtt/legacy-promotion-and-schema-evo

Conversation

@g-talbot
Copy link
Copy Markdown
Contributor

Stacked on #6410. Two adversarial-review follow-ups grouped because they share the streaming engine's input-routing and union-schema seams.

(b) Legacy-prefix promotion

A new operation type pairs a rg_partition_prefix_len = 0 split with prefix_len > 0 peers in one merge, so legacy splits can be folded into prefix-aligned buckets instead of aging out via retention. Adds:

  • ParquetMergeOperation::promote_legacy(splits, target_prefix_len): relaxes MP-3 to allow mixed rg_partition_prefix_len as long as every input is <= target. Sort_fields + window equality unchanged.
  • ParquetMergeOperation::target_prefix_len_override: Option<u32> field records the promotion target; None is the default regular-merge form.
  • merge_parquet_split_metadata(..., mixed_prefix_ok): skips the input-side prefix-len equality check in promotion mode. The output prefix_len still comes from the writer's KV stamp via MergeOutputFile.output_rg_partition_prefix_len (CS-1 holds by construction post-F1).
  • merge::execute_merge_operation(op, sources, ...): new thin executor that opens each input as either LegacyInputAdapter (when split.rg_partition_prefix_len < target) or StreamingParquetReader (otherwise), then feeds them to the streaming engine. Becomes the seam PR-7 will wire from above.

F6 + F13: Body-col schema evolution

The merger now supports MC-4 across heterogeneous body-col schemas.

  • F6: normalize_type collapses Binary/LargeBinary (and dict variants) to Binary, analogous to the existing string-flavour collapse. Two inputs whose body col differs only by byte-array flavour merge cleanly; before this they hit a "type conflict" at alignment time.
  • F13: streaming_writer.rs::write_list_via_serialized_column_writer (renamed from ..._non_nullable_...) now handles nullable outer List<T> / LargeList<T>. MC-4 forces the union to be nullable when a List col is present in only some inputs; before this the writer rejected the merged output. Uses Dremel max_def_level = 2 (0 = outer null, 1 = empty list, 2 = element present) for nullable outer; non-nullable path unchanged.

Test plan

  • cargo nextest run -p quickwit-parquet-engine --all-features — 493 unit tests pass locally.
  • cargo clippy -p quickwit-parquet-engine --tests --all-features with -Dwarnings.
  • cargo doc --no-deps -p quickwit-parquet-engine warning-free.
  • cargo fmt --all -- --check (nightly via PATH override).
  • New tests covering the new paths:
    • Planner: test_promote_legacy_pairs_legacy_with_aligned_peer, test_promote_legacy_rejects_higher_prefix_input, test_promote_legacy_still_enforces_sort_fields, test_promote_legacy_all_at_target_is_valid.
    • Aggregator: test_mixed_prefix_ok_skips_input_equality_check.
    • Executor end-to-end: test_promote_legacy_executor_end_to_end (legacy single-RG + aligned multi-RG → 3-RG aligned output passing assert_unique_rg_prefix_keys + CS-1).
    • Executor negative: test_executor_mismatched_sources_count_bails.
    • Schema-evo: test_mc2_mixed_schemas_round_trip covers Utf8↔Dict, LargeBinary↔Binary, List<Float64> in A only, A-only Int32, B-only Int64, plus a common Float64 control col.

Out of scope / follow-up

  • Cross-file SS-3 ordering: the F12 missing-col constant uses [0x00, 0x00] which sorts before non-null values. Fine for single-file SS-3 (constant across all RGs); needs a 0xff-prefixed sentinel if cross-file mixed-schema SS-3 becomes a production scenario.
  • Wiring execute_merge_operation into the compaction executor (PR-7): the seam is now in place, the actual planner integration that emits promote_legacy operations is a separate change.

🤖 Generated with Claude Code

@g-talbot g-talbot requested a review from a team as a code owner May 13, 2026 15:13
@g-talbot g-talbot force-pushed the gtt/legacy-promotion-and-schema-evo branch from 4559ea3 to 2a3f09a Compare May 13, 2026 15:25
@g-talbot g-talbot changed the base branch from gtt/streaming-merge-engine-multi-rg to gtt/streaming-merge-engine-multi-rg-3-hardening May 13, 2026 15:25
@g-talbot g-talbot force-pushed the gtt/legacy-promotion-and-schema-evo branch from 2a3f09a to edd45a6 Compare May 13, 2026 18:11
@g-talbot g-talbot force-pushed the gtt/streaming-merge-engine-multi-rg-3-hardening branch from 90422ae to 471f7e6 Compare May 14, 2026 13:48
@g-talbot g-talbot force-pushed the gtt/legacy-promotion-and-schema-evo branch from edd45a6 to 34d634f Compare May 14, 2026 13:48
@g-talbot g-talbot force-pushed the gtt/streaming-merge-engine-multi-rg-3-hardening branch from 471f7e6 to d351a04 Compare May 14, 2026 14:23
@g-talbot g-talbot force-pushed the gtt/legacy-promotion-and-schema-evo branch from 34d634f to a6c72d5 Compare May 14, 2026 14:23
Two adversarial-review follow-ups grouped because they share the
streaming engine's input-routing and union-schema seams.

## (b) Legacy-prefix promotion

A new operation type pairs a prefix_len=0 split with prefix_len>0
peers in one merge, so legacy splits can be folded into prefix-
aligned buckets instead of aging out via retention. Adds:

- `ParquetMergeOperation::promote_legacy(splits, target_prefix_len)`: relaxes MP-3 to allow mixed
  `rg_partition_prefix_len` as long as every input is `<= target`. Sort_fields + window equality
  unchanged.
- `ParquetMergeOperation::target_prefix_len_override: Option<u32>` field records the promotion
  target; `None` is the default regular-merge form.
- `merge_parquet_split_metadata(..., mixed_prefix_ok)`: skips the input-side prefix-len equality
  check in promotion mode. The output prefix_len still comes from the writer's KV stamp via
  `MergeOutputFile.output_rg_partition_prefix_len` (CS-1 holds by construction post-F1).
- `merge::execute_merge_operation(op, sources, ...)`: new thin executor that opens each input as
  either `LegacyInputAdapter` (when `split.rg_partition_prefix_len < target`) or
  `StreamingParquetReader` (otherwise), then feeds them to the streaming engine. Becomes the seam
  PR-7 will wire from above.

Tests:
- `test_promote_legacy_pairs_legacy_with_aligned_peer`, `test_promote_legacy_rejects_higher_prefix_input`,
  `test_promote_legacy_still_enforces_sort_fields`, `test_promote_legacy_all_at_target_is_valid`.
- `test_mixed_prefix_ok_skips_input_equality_check`.
- `test_promote_legacy_executor_end_to_end`: legacy single-RG + aligned multi-RG → 3-RG output
  passing `assert_unique_rg_prefix_keys` with `prefix_len = 1`, plus metastore CS-1.
- `test_executor_mismatched_sources_count_bails`.

## F6 + F13: Schema evolution for body columns

The merger now supports MC-4 across heterogeneous body-col schemas:

- F6: `normalize_type` collapses `Binary`/`LargeBinary` (and dict variants) to `Binary`, analogous
  to the existing string-flavour collapse. Two inputs whose body col differs only by byte-array
  flavour merge cleanly; before this they hit a "type conflict" at alignment time.
- F13: `streaming_writer.rs::write_list_via_serialized_column_writer` (renamed from
  `..._non_nullable_...`) now handles nullable outer `List<T>` / `LargeList<T>`. MC-4 forces the
  union to be nullable when a List col is present in only some inputs; before this the writer
  rejected the merged output. Uses Dremel max_def_level = 2 (0 = outer null, 1 = empty list, 2 =
  element present) for nullable outer; non-nullable path unchanged.

Test: `test_mc2_mixed_schemas_round_trip` builds two inputs A and B
with the same sort schema but different body cols (Utf8 vs
Dict<Utf8>, LargeBinary vs Binary, List<Float64> in A only, Int32
A-only, Int64 B-only, common Float64). The merge produces the
union schema; per-row rendering via `render_cell` matches across
flavour boundaries; List cells from B render as nulls.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@g-talbot g-talbot force-pushed the gtt/streaming-merge-engine-multi-rg-3-hardening branch from d351a04 to 72ade96 Compare May 14, 2026 14:49
@g-talbot g-talbot force-pushed the gtt/legacy-promotion-and-schema-evo branch from a6c72d5 to 2efe6c2 Compare May 14, 2026 14:49
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.

1 participant