Do not apply read-in-order optimizations to STREAM reads by groeneai · Pull Request #108568 · ClickHouse/ClickHouse · GitHub
Skip to content

Do not apply read-in-order optimizations to STREAM reads#108568

Merged
alexey-milovidov merged 4 commits into
ClickHouse:masterfrom
groeneai:fix-streaming-distinct-in-order-crash
Jun 29, 2026
Merged

Do not apply read-in-order optimizations to STREAM reads#108568
alexey-milovidov merged 4 commits into
ClickHouse:masterfrom
groeneai:fix-streaming-distinct-in-order-crash

Conversation

@groeneai

@groeneai groeneai commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Related: #108554

Changelog category (leave one):

  • Bug Fix (user-visible misbehavior in an official stable release)

Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):

Fix a logical error (Equal values are not contiguous within the range assumed to be sorted) when running DISTINCT over a STREAM read (SELECT DISTINCT ... FROM table STREAM). Read-in-order optimizations are no longer applied to STREAM reads, which produce rows in commit order rather than sorting-key order.

Description

A STREAM read (FROM ... STREAM, enable_streaming_queries = 1) returns parts in commit order via MergeTreeCommitOrderSequentialSource, not in sorting-key order, so its output is not globally sorted by the table sorting key.

checkSupportedReadingStep in optimizeReadInOrder.cpp still accepted such a ReadFromMergeTree (the sorting key is non-empty), so the query plan optimizer requested read-in-order on it. The read then advertised the sorting-key order (input_order_info -> getSortDescription()), and order-dependent transforms were applied on top of unsorted data.

For DISTINCT in order this feeds unsorted data to DistinctSortedStreamTransform, which aborts the server in debug/sanitizer builds:

Logical error: 'Equal values are not contiguous within the range assumed to be sorted'.
  src/Columns/findEqualRangeEndAssumeSorted.h:85
  src/Columns/ColumnString.cpp:816  DB::ColumnString::getEqualRangeEndAssumeSorted
  src/Core/SortCursor.h:776
  src/Processors/Transforms/DistinctSortedStreamTransform.cpp:184  DB::DistinctSortedStreamTransform::transform

The same phantom order silently affects aggregation-in-order and LIMIT BY-in-order on STREAM reads.

Fix: reject streaming reads in checkSupportedReadingStep, so none of the in-order optimizations (read-in-order, aggregation-in-order, distinct-in-order, limit-by-in-order) are applied to a STREAM read. STREAM is only valid on StorageMergeTree/StorageReplicatedMergeTree (both produce ReadFromMergeTree), so the single guard covers all streaming reads; the ReadFromMerge/ReadFromObjectStorageStep branches cannot be streaming.

Reproducer (crashes before the fix, returns correctly after):

CREATE TABLE t (a String, b UInt64) ENGINE = MergeTree ORDER BY a;
INSERT INTO t SELECT toString(number % 100), number FROM numbers(5000);
INSERT INTO t SELECT toString(number % 100), number FROM numbers(5000);
INSERT INTO t SELECT toString(number % 100), number FROM numbers(5000);
SELECT count() FROM (SELECT DISTINCT a FROM t STREAM LIMIT 50) SETTINGS enable_streaming_queries = 1;

Found by the AST fuzzer on the Stress test (arm_asan_ubsan, s3) check (STID 2508-4156, query SELECT count() FROM (SELECT DISTINCT * FROM t_streaming_test STREAM LIMIT 10)):
https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=108554&sha=bdbb85719cfc04f07cc5084b3e4a932df073b419&name_0=PR&name_1=Stress%20test%20%28arm_asan_ubsan%2C%20s3%29

Version info

  • Merged into: 26.7.1.273

A STREAM read returns parts in commit order, not sorting-key order, so its
output is not globally sorted by the sorting key even though the key is
non-empty. checkSupportedReadingStep still accepted such a ReadFromMergeTree,
so the query plan optimizer requested read-in-order on it. The read then
advertised the sorting-key order (via input_order_info) and fed unsorted data
to order-dependent transforms.

For DISTINCT in order this aborts the server in debug/sanitizer builds with
'Equal values are not contiguous within the range assumed to be sorted'
(DistinctSortedStreamTransform). The same phantom order silently affects
aggregation-in-order and LIMIT BY-in-order.

Reject streaming reads in checkSupportedReadingStep so none of the in-order
optimizations (read-in-order, aggregation-in-order, distinct-in-order,
limit-by-in-order) are applied to a STREAM read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@groeneai

Copy link
Copy Markdown
Contributor Author

@groeneai

Copy link
Copy Markdown
Contributor Author

cc @KochetovNicolai @novikd — could you review this? A STREAM read returns parts in commit order (not sorting-key order), but checkSupportedReadingStep still accepted it for read-in-order, so the read advertised the sorting-key order and fed unsorted data to DISTINCT-in-order (DistinctSortedStreamTransform), aborting with 'Equal values are not contiguous'. The fix excludes streaming reads from all in-order optimizations at checkSupportedReadingStep.

@nikitamikhaylov nikitamikhaylov added the can be tested Allows running workflows for external contributors label Jun 26, 2026
@clickhouse-gh

clickhouse-gh Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [9732127]

Summary:


AI Review

Summary

This PR disables in-order read optimizations for STREAM reads by rejecting streaming ReadFromMergeTree steps before read-in-order, aggregation-in-order, distinct-in-order, and limit-by-in-order can request input_order_info. The current code matches the stated root cause: valid STREAM queries only reach ReadFromMergeTree, ReadFromMergeTree advertises storage order only after requestReadingInOrder, and the old window-ordering helper cannot process STREAM because the old analyzer rejects it before planning. The previous inline test concern has been addressed by pinning the optimizer settings.

Final Verdict

Status: ✅ Approve

@clickhouse-gh clickhouse-gh Bot added the pr-bugfix Pull request with bugfix, not backported by default label Jun 26, 2026
Comment thread tests/queries/0_stateless/04411_streaming_distinct_in_order.sql
STREAM reads are Linux-only (the server raises SUPPORT_IS_DISABLED for
hasStream() under #ifndef OS_LINUX in QueryAnalyzer). The test was running
on the macOS Fast test (arm_darwin) and failing with SUPPORT_IS_DISABLED.
no-darwin skips it only on macOS while keeping full coverage on every Linux
job, including the Linux Fast test where the STREAM crash repro is valid.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@groeneai

Copy link
Copy Markdown
Contributor Author

Pushed 12998180cac: tag 04411_streaming_distinct_in_order as no-darwin.

The previous head failed Fast test (arm_darwin) with Code: 344. Streaming requests are supported only on Linux. (SUPPORT_IS_DISABLED) — the macOS Fast test ran the new test, but STREAM reads are Linux-only (#ifndef OS_LINUX guard in QueryAnalyzer). no-darwin skips it only on macOS; it still runs (and the crash repro is still exercised) on every Linux job, including the Linux Fast test. Verified 5/5 OK on Linux with randomization; the tag is a no-op there.

Session id: cron:clickhouse-worker-slot-1:20260626-162000

The stateless runner randomizes optimizer settings. The regression depends on
the query-plan DISTINCT-in-order pass being enabled; if optimize_distinct_in_order
or query_plan_enable_optimizations is randomized off, the query uses the generic
distinct path and the unfixed build never reaches the bad input_order_info, so the
test could pass without the fix. Pin enable_analyzer, query_plan_enable_optimizations
and optimize_distinct_in_order so the test deterministically exercises the path the
PR guards. Verified: with the pins the test crashes on the pre-fix binary and passes
on the fixed binary; with the pins removed the pre-fix binary no longer crashes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@groeneai

Copy link
Copy Markdown
Contributor Author

Addressed the review on 04411_streaming_distinct_in_order.sql: pinned the three trigger settings so the test deterministically exercises the query-plan DISTINCT-in-order pass this PR guards, instead of only the default configuration.

SET enable_analyzer = 1;
SET query_plan_enable_optimizations = 1;
SET optimize_distinct_in_order = 1;

Re-verified both directions locally (debug build):

  • Fixed binary, pins on: returns 50/50, no crash.
  • Pre-fix binary (guard reverted), pins on: aborts with the original Equal values are not contiguous within the range assumed to be sorted in DistinctSortedStreamTransform. So the pinned test catches the regression.
  • Pre-fix binary, optimize_distinct_in_order=0 or query_plan_enable_optimizations=0: returns 50/50, no crash. This confirms those settings are exactly what route the query into the bad path, which is why the test would have silently passed without the fix when CI randomized them off.
  • Runner with randomization on (fixed binary): 10/10 OK.

Pushed in 1e00156.

@groeneai

Copy link
Copy Markdown
Contributor Author

CI finish ledger - 1e00156

CI fully finished (Finish Workflow and Style check passed; no checks queued or in progress; >20 min since the last check completed). Every failure below has an owner.

Check / test Reason Owner / fixing PR
Integration tests (arm_binary, distributed plan, 2/4) / test_keeper_disks::test_snapshots_with_disks flaky (chronic; 18+ unrelated PRs + master in 30d; a Keeper integration test, unrelated to this PR's query-optimizer change in optimizeReadInOrder.cpp) #108513 (ours, open)
CH Inc sync private sync CH Inc sync (private, not actionable)

Session id: cron:our-pr-ci-monitor:20260627-003000

@alexey-milovidov

Copy link
Copy Markdown
Member

@groeneai, as #108513 was merged, update the branch here.

Pick up merged PR ClickHouse#108513 (fix flaky test_keeper_disks::test_snapshots_with_disks)
per @alexey-milovidov request on PR ClickHouse#108568.
@groeneai

Copy link
Copy Markdown
Contributor Author

@alexey-milovidov done. Merged current master into the branch (head 9732127), so it now includes the merged #108513. CI is re-running.

@clickhouse-gh

clickhouse-gh Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 85.40% 85.50% +0.10%
Functions 92.60% 92.60% +0.00%
Branches 77.70% 77.70% +0.00%

Changed lines: Changed C/C++ lines covered by tests: 4/5 (80.00%) | Lost baseline coverage: none · Uncovered code

Full report · Diff report

@groeneai

Copy link
Copy Markdown
Contributor Author

CI finish ledger — 9732127

CI fully finished on this head (Finish Workflow + Mergeable Check pass; no checks queued/in_progress; >20-min ingestion buffer). After merging master to pick up the now-merged #108513, every check is green and the previously-noted flaky test_keeper_disks::test_snapshots_with_disks ledger line is cleared.

Check / test Reason Owner / fixing PR
(none) All checks green on this head
Sync CH Inc sync (private, not actionable)

Session id: cron:our-pr-ci-monitor:20260629-090000

@alexey-milovidov alexey-milovidov self-assigned this Jun 29, 2026
@alexey-milovidov alexey-milovidov added this pull request to the merge queue Jun 29, 2026
Merged via the queue into ClickHouse:master with commit fac0764 Jun 29, 2026
174 checks passed
@robot-clickhouse robot-clickhouse added the pr-synced-to-cloud The PR is synced to the cloud repo label Jun 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-bugfix Pull request with bugfix, not backported by default pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants