Skip to content

fix(storage): keep the input order within a partition on hash writes - #20896

Open
vbmithr wants to merge 2 commits into
databendlabs:mainfrom
vbmithr:fix-keep-input-continuity
Open

vbmithr wants to merge 2 commits into
databendlabs:mainfrom
vbmithr:fix-keep-input-continuity

Conversation

@vbmithr

@vbmithr vbmithr commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

I hereby agree to the terms of the CLA available at: https://docs.databend.com/dev/policies/cla/

Summary

Part of #20711 (preserving the continuity of an ordered input through the write path, as discussed there).

build_table_write_layout_with_schema routes rows to lanes by partition key and then sorts each lane by the partition key only. That sort is not stable, so the rows of a partition lose the order they arrived in. Loading an input already sorted by the cluster key into a table with PARTITION BY … WRITE_DISTRIBUTION_MODE = 'hash' therefore writes overlapping blocks, even with max_threads = 1.

Each row now carries its arrival position, counted across the input lanes (a block takes the next range of positions when it arrives), as the last sort key after the partition key. The position column is projected away with the partition-key columns after the sort, so each partition keeps the order its rows arrived in.

No setting, no new sort and no ordering guarantee for PARTITION BY: the lane sort already exists and only gains a tie-breaker, so equal partition keys keep their arrival order instead of an arbitrary one. An unpartitioned table already writes a sorted input's blocks contiguously; this removes the reordering that only the hash layout adds. Serialization and writes stay parallel.

Scope: one ordered input (one file, or an ordered query result) keeps its order in every partition. When several files are read in parallel, their rows arrive interleaved, so the arrival order is not the input order; restoring it before the write (e.g. an order-restoring gather of parallel Parquet reads) is a separate follow-up for #20711.

Measured on main (6c7f657), 10M rows sorted by k (200 keys), PARTITION BY (v % 2) CLUSTER BY (k) ROW_PER_BLOCK = 100000 WRITE_DISTRIBUTION_MODE = 'hash'. Block pairs of one partition whose k ranges strictly overlap (0 = each partition's blocks are contiguous):

input before, 1 thread before, 8 threads after, 1 thread after, 8 threads
1 CSV file 192 195 0 0
1 Parquet file 293 293 0 0
12 CSV files 229 588 0 377
4 Parquet files 397 1309 0 144

The last two rows at 8 threads are the multi-file case left for the follow-up.

Tests

  • Unit Test
  • Logic Test
  • Benchmark Test
  • No Test - Explain why

A new case at the end of 09_0054_partition_by.test copies one Parquet file ordered by k (400k rows) into a hash-partitioned table and checks that no two blocks of a partition overlap on k beyond a shared endpoint; an unordered control does overlap. On main the ordered case fails (119 overlapping pairs); with this change it passes, standalone and on a local 2-node cluster.

Also run: 09_0041_auto_compaction, 09_0016_remote_alter_recluster, 03_0028_copy_into_stage (09_0041 has the same 6 failures on the local cluster with and without this change).

Type of change

  • Bug Fix (non-breaking change which fixes an issue)
  • New Feature (non-breaking change which adds functionality)
  • Breaking Change (fix or feature that could cause existing functionality not to work as expected)
  • Documentation Update
  • Refactoring
  • Performance Improvement
  • Other (please describe):

AI assistance

  • AI usage: An AI coding agent reproduced the cases, drafted the change, the logic test and this description; I reviewed the full diff
  • Responsible human: @vbmithr
  • The responsible human has read every line of this diff and can explain each change

This change is Reviewable

The hash write layout routes rows to lanes by partition key and sorts
each lane by the partition key only. That sort is not stable, so the
rows of a partition lost the order they arrived in: an input sorted by
the cluster key, loaded into a table with
WRITE_DISTRIBUTION_MODE = 'hash', wrote overlapping blocks even with a
single thread.

Each row now carries its arrival position, counted across the input
lanes, as a last sort key, projected away after the sort. The compaction
after the layout uses OrderedBlockCompactBuilder, which never emits a
large block ahead of pending smaller ones.

Refs databendlabs#20711.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmvURX4AcHbn5WuJr7gmQG
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-31T08:22:17.921529Z
We couldn’t complete this review. Comment @codex review to try again.
f3c6dbb Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions github-actions Bot added the pr-bugfix this PR patches a bug in codebase label Oct 29, 2026
@vbmithr

vbmithr commented Oct 29, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

// BlockCompactBuilder would pass a large block through ahead of
// pending smaller ones; keep the arrival order instead.
pipeline.add_accumulating_transformer(|| {
OrderedBlockCompactBuilder::new(block_thresholds, 0)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is a specialized modification. Should PARTITION BY guarantee an ordering? I don’t believe such a rigid substitution is appropriate, nor should it be enabled via configuration, as it potentially alters the semantics themselves.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed, removed. The compactor swap wasn't needed: with only the tie-breaker the ordered loads still write contiguous blocks in every partition. What remains doesn't make PARTITION BY guarantee an ordering. The hash layout already sorts each lane by the partition key; the change only breaks ties by arrival position, so equal partition keys keep their arrival order instead of an arbitrary one. An unpartitioned table already writes a sorted input's blocks contiguously (0 overlapping pairs in the table on #20711), while the same input into a hash-partitioned table gets 192–293 even with one thread. This removes that difference, with no setting.

@@ -0,0 +1,110 @@
statement ok
create or replace database db_09_0103

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Given current trends in AI development, I believe we should avoid having every change result in a new SLT file; instead, we should aim to incorporate these tests into existing, similar SLT files.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done: the case is now a section at the end of 09_0054_partition_by.test (fails on main with 119 overlapping pairs, passes with the fix, standalone and on a 2-node cluster), and the separate file is gone.

@github-actions

Copy link
Copy Markdown
Contributor

⏱ CI timing · Dev

run 40920334868 · attempt 2 (wall time includes re-run gap) · 25.4 min (main p50 55.4 min, −30.0 min)

stage this run main p50 Δ
total 25.4 min 55.4 min −30.0 min
build (aarch64, ci profile) 15.0 min 20.1 min −5.2 min
├ deps + crates (parallel) 2.5 min — —
├ databend-query codegen 3.1 min — —
├ databend-binaries link 7.6 min — —
└ sccache hit rate 99.3 % — —
test_unit 16.5 min 17.4 min −0.9 min
mac_check — 13.5 min —
tests after build (49 jobs) 9.8 min 25.7 min −15.9 min
├ queue p50 / max 1.7 min / 4.0 min 5.0 min / 24.0 min −3.3 min
├ sqllogic standalone (8 jobs, max) 4.3 min 4.2 min +0.0 min
├ sqllogic cluster (8 jobs, max) 8.1 min 7.2 min +0.9 min
├ sqllogic stage (2 jobs, max) 4.8 min 5.0 min −0.1 min
├ sqllogic other (10 jobs, max) 6.1 min 5.7 min +0.3 min
├ stateless (2 jobs, max) 7.2 min 6.7 min +0.5 min
├ stateful (4 jobs, max) 5.1 min 4.7 min +0.4 min
├ compat (6 jobs, max) 5.9 min 5.7 min +0.3 min
├ meta / logs / lineage / udf (5 jobs, max) 4.5 min 4.4 min +0.1 min
├ ee (2 jobs, max) 1.4 min 1.4 min +0.0 min
├ private_tasks (1 jobs, max) 8.8 min 8.8 min −0.1 min
└ fuzz (1 jobs, max) 1.0 min 1.1 min −0.1 min

Minutes unless stated; durations exclude queue time unless stated; "max" = longest job in the group. Baseline = median of the last 10 successful runs (Dev: Main on main; Cloud: recent Cloud runs). Build breakdown has no baseline. Updated on each push.

Review: the tie-breaker in the hash layout's sort is enough on its own.
Without the switch to OrderedBlockCompactBuilder the ordered loads still
write contiguous blocks in every partition, so the hash path keeps
BlockCompactBuilder.

The regression case moves into 09_0054_partition_by.test: one ordered
Parquet file copied into a hash-partitioned table (119 overlapping block
pairs on main, 0 with the fix, standalone and on a 2-node cluster), and
an unordered control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SmvURX4AcHbn5WuJr7gmQG
@vbmithr

vbmithr commented Oct 30, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@vbmithr

vbmithr commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Something went wrong. Try again later by commenting “@codex review”.

An unknown error occurred
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

This branch has not been deployed

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

Labels

pr-bugfix this PR patches a bug in codebase

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants