Skip to content

[fix](binlog) Fix BE crash when APPEND_ONLY row-binlog scan omits key columns - #66432

Merged
morningman merged 1 commit into
apache:masterfrom
morningman:wt-fix-binlog
Aug 6, 2026
Merged

[fix](binlog) Fix BE crash when APPEND_ONLY row-binlog scan omits key columns#66432
morningman merged 1 commit into
apache:masterfrom
morningman:wt-fix-binlog

Conversation

@morningman

@morningman morningman commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #66390

Related PR: #63850

Related Issue: #65418

Problem Summary:

A BE received SIGSEGV in VMergeIteratorContext::compare() while VMergeIterator::init() was building its merge heap during a ROW-binlog regression workload.

Root cause chain:

  1. Any row-binlog scan (binlog_scan_type != NONE, including APPEND_ONLY) forced a key-ordered merged read with read_orderby_key_num_prefix_columns == 0.
  2. With a zero prefix, read_orderby_key_columns stays null, so both merge comparators (VMergeIteratorContext::compare() at the rowset level and VCollectIterator::LevelIteratorComparator at the rowset-tree level) fall back to comparing the first num_key_columns block positions. Schema::num_key_columns() counts the key columns of the whole tablet schema, independent of the projection, so the fallback silently assumes the projection starts with the full ordered key prefix.
  3. MIN_DELTA / DETAIL satisfy that contract because they widen the storage projection with every key column. APPEND_ONLY takes the direct_mode branch (return_columns = SQL projection), which may omit some or all key columns (e.g. SELECT v1 FROM t@incr(... "incrementType" = "APPEND_ONLY") on a table with two key columns).
  4. Block::compare_at() has only DCHECK bounds checks, so in a release build the first heap comparison reads past the block's column array and kills the BE. This is reachable whenever a rowset has more than one segment (force_key_ordered_read makes is_merge_iterator() true even for non-overlapping segments) or rowsets overlap. A projection that has enough columns but not the leading keys would instead be compared on the wrong columns — a silent misordering.

Fix (three layers):

  1. Root cause (olap_scanner.cpp): only MIN_DELTA / DETAIL — the modes that actually group by key and reconstruct BEFORE/AFTER rows, and whose projection is widened with the full key prefix — force the key-ordered merged read. APPEND_ONLY does no key grouping (it is a plain op-filter + TSO-range stream), so it now reads unordered like a plain scan, which also removes an unnecessary merge-heap cost from the highest-throughput mode. ORDER BY / TopN pushdown params stay disabled for all binlog scan types, as before.
  2. Defense in VMergeIteratorContext::init(): validate the compare contract once the first block is loaded — explicit compare columns must point inside the block; the default key-prefix comparison requires the projection to start with exactly the schema's key column ids in order; the sequence tie-break position must be in range. Violations return an InternalError carrying tablet/rowset/version/projection details instead of an out-of-bounds read.
  3. Same defense in VCollectIterator::Level1Iterator::init(): validate every child's first block against the same contract before anything is pushed into the merge heap.

Release note

Fixed a BE crash (SIGSEGV in the storage merge comparator) when an APPEND_ONLY row-binlog incremental scan projected fewer columns than the table's key columns. APPEND_ONLY binlog scans no longer force a key-ordered storage read; their row order (without ORDER BY) may differ from before.

Check List (For Author)

  • Test

    • Regression test
    • Unit Test
    • Manual test (add detailed scripts or steps below)
    • No need to test or manual test. Explain why:
      • This is a refactor/code format and no logic has been changed.
      • Previous test can cover this change.
      • No code files have been changed.
      • Other reason
  • Behavior changed:

    • No.
    • Yes. APPEND_ONLY row-binlog scans no longer force a per-tablet key-ordered read, so their output order without ORDER BY may change (SQL never guaranteed it; existing binlog/table-stream regression suites all use order-insensitive assertions or explicit ORDER BY). Queries that used to crash or silently mis-merge now either work (APPEND_ONLY) or fail with a diagnosable InternalError (contract violations).
  • Does this need documentation?

    • No.
    • Yes.

Check List (For Reviewer who merge this PR)

  • Confirm the release note
  • Confirm test cases
  • Confirm document
  • Add branch pick label

🤖 Generated with Claude Code

… columns

Any row-binlog scan used to force a key-ordered merged read with
read_orderby_key_num_prefix_columns == 0, which makes the merge
comparators (VMergeIteratorContext::compare and VCollectIterator's
LevelIteratorComparator) fall back to comparing the first
num_key_columns block positions, where num_key_columns counts the key
columns of the whole tablet schema.

MIN_DELTA / DETAIL scans satisfy that contract because they widen the
storage projection with the full key prefix. APPEND_ONLY, however, takes
the direct_mode branch and keeps the raw SQL projection, which may omit
some or all key columns. Block::compare_at() only has DCHECK bounds
checks, so in a release build the first heap comparison reads past the
block's column array and crashes the BE with SIGSEGV inside
VMergeIterator::init / std::push_heap.

Fix:
- olap_scanner: only MIN_DELTA / DETAIL force the key-ordered merged
  read. APPEND_ONLY does no key grouping, so it now reads unordered like
  a plain scan (ORDER BY / TopN pushdowns stay disabled for all binlog
  scan types as before).
- VMergeIteratorContext::init and VCollectIterator::Level1Iterator::init
  now validate the merge compare contract (compare positions exist in
  the block, and the projection starts with the full ordered key prefix
  for the default comparison) and return a diagnosable error instead of
  performing an out-of-bounds or silently misordered comparison.
- Add BE unit tests for the rejected projections and a regression suite
  covering APPEND_ONLY / DETAIL / MIN_DELTA with narrow projections.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@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?

@morningman

Copy link
Copy Markdown
Contributor Author

run buildall

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-H: Total hot run time: 28982 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpch-tools
Tpch sf100 test result on commit 40762c79bd014d66a843cf21ab2a1464e5bac092, data reload: false

------ Round 1 ----------------------------------
============================================
q1	17595	3877	3888	3877
q2	2001	335	203	203
q3	10308	1388	798	798
q4	4678	471	340	340
q5	7523	851	543	543
q6	173	166	132	132
q7	728	797	587	587
q8	9313	1578	1629	1578
q9	5343	4055	4048	4048
q10	6755	1627	1337	1337
q11	503	354	327	327
q12	713	581	447	447
q13	18065	3218	2718	2718
q14	261	260	236	236
q15	q16	746	735	662	662
q17	957	892	949	892
q18	6561	5594	5518	5518
q19	1177	1279	1107	1107
q20	855	680	590	590
q21	5725	2962	2731	2731
q22	478	375	311	311
Total cold run time: 100458 ms
Total hot run time: 28982 ms

----- Round 2, with runtime_filter_mode=off -----
============================================
q1	4758	4733	4550	4550
q2	303	330	211	211
q3	4871	5214	4699	4699
q4	2230	2253	1403	1403
q5	4448	4449	4602	4449
q6	237	173	127	127
q7	1824	1728	1658	1658
q8	2356	2004	1999	1999
q9	7211	7120	6860	6860
q10	4253	4210	3804	3804
q11	521	375	332	332
q12	685	715	484	484
q13	2940	3291	2741	2741
q14	280	277	250	250
q15	q16	659	677	598	598
q17	1212	1199	1196	1196
q18	12108	10985	11778	10985
q19	1061	1064	1042	1042
q20	2184	2199	1940	1940
q21	5257	4450	4502	4450
q22	512	473	400	400
Total cold run time: 59910 ms
Total hot run time: 54178 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
TPC-DS: Total hot run time: 166063 ms
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/tpcds-tools
TPC-DS sf100 test result on commit 40762c79bd014d66a843cf21ab2a1464e5bac092, data reload: false

query5	4318	579	458	458
query6	470	223	213	213
query7	4888	600	342	342
query8	329	161	146	146
query9	8788	4004	3977	3977
query10	462	346	296	296
query11	5836	2190	1994	1994
query12	152	99	96	96
query13	1234	573	442	442
query14	6054	4241	3984	3984
query14_1	3786	3773	3790	3773
query15	201	201	181	181
query16	990	457	449	449
query17	955	665	536	536
query18	2418	466	323	323
query19	209	186	144	144
query20	100	102	99	99
query21	231	160	130	130
query22	12999	12986	12759	12759
query23	15590	15095	14514	14514
query23_1	14559	14592	14639	14592
query24	7615	1700	1233	1233
query24_1	1231	1228	1235	1228
query25	522	434	353	353
query26	1302	362	213	213
query27	2597	616	378	378
query28	4550	2040	2014	2014
query29	1039	582	477	477
query30	344	257	222	222
query31	1171	1110	1045	1045
query32	114	63	58	58
query33	513	304	243	243
query34	1196	1099	625	625
query35	753	734	630	630
query36	767	773	697	697
query37	151	104	92	92
query38	1804	1767	1709	1709
query39	828	809	797	797
query39_1	782	795	789	789
query40	239	159	142	142
query41	64	63	60	60
query42	94	93	91	91
query43	322	315	273	273
query44	1424	783	760	760
query45	185	177	174	174
query46	1035	1136	690	690
query47	1572	1548	1477	1477
query48	427	427	307	307
query49	599	422	315	315
query50	1145	423	349	349
query51	10651	10800	10623	10623
query52	88	92	77	77
query53	261	274	216	216
query54	309	245	232	232
query55	77	72	67	67
query56	342	316	312	312
query57	1039	1003	928	928
query58	308	259	260	259
query59	1539	1594	1368	1368
query60	317	296	263	263
query61	176	178	176	176
query62	400	324	267	267
query63	234	202	206	202
query64	3011	1140	974	974
query65	3907	3802	3793	3793
query66	1820	487	384	384
query67	28220	28121	28006	28006
query68	3385	1574	1044	1044
query69	457	307	272	272
query70	891	763	772	763
query71	362	327	314	314
query72	3073	2634	2308	2308
query73	809	748	454	454
query74	4590	4485	4246	4246
query75	2363	2345	1979	1979
query76	2438	1122	779	779
query77	328	360	272	272
query78	11123	11102	10506	10506
query79	1349	1230	777	777
query80	664	544	481	481
query81	450	332	281	281
query82	605	174	136	136
query83	411	322	305	305
query84	338	165	128	128
query85	955	610	539	539
query86	320	231	238	231
query87	1979	1992	1807	1807
query88	3715	2773	2793	2773
query89	386	317	281	281
query90	1915	191	188	188
query91	202	193	157	157
query92	62	58	53	53
query93	1575	1490	924	924
query94	533	368	305	305
query95	759	577	464	464
query96	1116	759	364	364
query97	2476	2467	2352	2352
query98	199	188	209	188
query99	735	724	610	610
Total cold run time: 252220 ms
Total hot run time: 166063 ms

@hello-stephen

Copy link
Copy Markdown
Contributor
ClickBench: Total hot run time: 23.93 s
machine: 'aliyun_ecs.c7a.8xlarge_32C64G'
scripts: https://github.com/apache/doris/tree/master/tools/clickbench-tools
ClickBench test result on commit 40762c79bd014d66a843cf21ab2a1464e5bac092, data reload: false

query1	0.00	0.00	0.00
query2	0.10	0.05	0.04
query3	0.27	0.14	0.14
query4	1.60	0.14	0.14
query5	0.23	0.22	0.23
query6	1.17	0.84	0.82
query7	0.04	0.00	0.00
query8	0.06	0.04	0.04
query9	0.36	0.31	0.31
query10	0.58	0.59	0.55
query11	0.19	0.14	0.14
query12	0.18	0.15	0.14
query13	0.46	0.47	0.49
query14	1.00	0.99	0.99
query15	0.60	0.58	0.58
query16	0.31	0.32	0.31
query17	1.10	1.15	1.08
query18	0.20	0.19	0.20
query19	2.03	1.95	1.96
query20	0.02	0.01	0.02
query21	15.44	0.22	0.13
query22	4.80	0.05	0.05
query23	16.13	0.30	0.12
query24	3.05	0.41	0.32
query25	0.13	0.06	0.05
query26	0.73	0.22	0.16
query27	0.04	0.04	0.04
query28	3.59	0.80	0.35
query29	12.54	4.06	3.18
query30	0.27	0.15	0.15
query31	2.79	0.58	0.32
query32	3.22	0.58	0.48
query33	3.13	3.25	3.17
query34	15.54	3.95	3.31
query35	3.23	3.21	3.22
query36	0.54	0.42	0.41
query37	0.09	0.06	0.06
query38	0.05	0.04	0.04
query39	0.03	0.03	0.03
query40	0.18	0.14	0.14
query41	0.09	0.03	0.03
query42	0.04	0.03	0.03
query43	0.04	0.03	0.04
Total cold run time: 96.19 s
Total hot run time: 23.93 s

@HappenLee

Copy link
Copy Markdown
Contributor

/review

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

Request changes. The production fix addresses the crash mechanism cleanly, but the new regression does not actually exercise the retained DETAIL/MIN_DELTA merge paths and does not follow the repository's required result/lifecycle conventions. I left two inline comments covering those gaps.

Critical checkpoint conclusions:

  • Goal and proof: APPEND_ONLY no longer requests the default full-key merge on a projection that may omit keys, and both storage heap layers validate every comparator position before insertion. The code-level goal is met, but the claimed merge-mode regression proof is incomplete because FE coerces both nominal DETAIL/MIN_DELTA queries on this DUP_KEYS table back to APPEND_ONLY.
  • Scope, reuse, and parallel paths: The source change is focused. APPEND_ONLY, MIN_DELTA, and DETAIL are separated at the scanner boundary; the VMerge and VCollect comparators receive equivalent contract checks. I found no missed production path after tracing FE op filtering, TSO pushdown, TabletReader, BetaRowsetReader, and BlockReader.
  • Concurrency, lifecycle, and memory: No new shared mutable state or lock ordering is introduced. Validation occurs after each first block is loaded and before heap insertion; later blocks retain the same schema. The checks add no steady-state allocation beyond the error path.
  • Error handling and observability: Contract violations now return non-OK Status values with projection and tablet/rowset/version context instead of allowing unchecked positional access. Status propagation through rowset/tablet reader initialization is preserved.
  • Configuration, compatibility, persistence, and writes: No product configuration, storage format, symbol, transaction, persistence, or FE-BE protocol change is introduced. The APPEND_ONLY physical order change is unspecified SQL order and is documented in the release note.
  • Performance: Removing the unnecessary APPEND_ONLY merge heap is beneficial; the new validation is once per initialized merge context/heap and is negligible relative to heap construction.
  • Tests: The narrow APPEND_ONLY reproducer has a deterministic overlapping-rowset topology under the row-binlog compaction freshness gate, and the new VMerge unit cases pass. The remaining blockers are the false DETAIL/MIN_DELTA coverage and the missing generated golden-output/test-lifecycle compliance called out inline.
  • User focus: No additional focus was provided, so the complete PR was reviewed.

Validation was static-only as required by the review runner. At the reviewed head, compile/format and nonConcurrent regression checks passed. The two added VGenericIterators tests passed in TeamCity; the aggregate BE-UT status is red for unrelated SegmentFlusher golden-byte failures, while the macOS BE-UT job failed at the linker on --exclude-libs.

Review completion: three rounds were completed. The final full-review and risk-focused agents all returned NO_NEW_VALUABLE_FINDINGS against this exact two-comment set, and every other candidate was deduplicated or dismissed with concrete code evidence.


// 1. Project a single value column: 1 projected column < 2 key columns.
// This is the exact shape that used to crash the BE.
assertEquals([[30], [31], [40], [41], [50], [51]],

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 follow the repository's regression-test contract for these determined results: use named qt_/order_qt_ cases and generate the matching .out file through the test runner instead of assertEquals(sql(...)). This ordinary single-table case should also hardcode the table name, and cleanup should remain at setup rather than dropping the database in finally, so failed state is preserved for debugging.

// 5. DETAIL / MIN_DELTA keep the forced key-ordered merge but widen the
// storage projection with the full key prefix internally; a narrow SQL
// projection must still work and return the same rows for a dup table.
assertEquals([[30], [31], [40], [41], [50], [51]],

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.

These two assertions do not exercise DETAIL or MIN_DELTA. OlapScanNode.parseBinlogScanType() forces every DUP_KEYS incremental scan to APPEND_ONLY, even when the SQL requests one of these modes, so both queries re-test the APPEND_ONLY branch and cannot validate the retained key-widening/forced-merge path. Please move this coverage to a UNIQUE KEY merge-on-write table with binlog.need_historical_value=true, include update/delete changes, and keep the narrow key-omitting projection so the BE actually receives TBinlogScanType::DETAIL and MIN_DELTA.

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

这个先不要这么修复了。
我们再做一个改动,把所有的列的规划都放到FE 里,这样BE 就不会出现自己加一些列了。

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

LGTM

@yiguolei

yiguolei commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

skip check_coverage

@github-actions github-actions Bot added the approved Indicates a PR has been approved by one committer. label Aug 6, 2026
@github-actions

github-actions Bot commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

PR approved by at least one committer and no changes requested.

@morningman
morningman merged commit 86c78b9 into apache:master Aug 6, 2026
36 of 37 checks passed
csun5285 added a commit to csun5285/doris that referenced this pull request Aug 7, 2026
### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#66432

Problem Summary: Storage readers maintained FE block positions, tablet-schema column IDs, predicate IDs, and delete-predicate columns through parallel mappings. Projection, nested-column pruning, virtual expressions, and row-binlog dependencies could make those mappings diverge. Use one ordered read schema as the reader coordinate, keep expected materialization types alongside physical columns, append storage-only delete-predicate dependencies after FE slots, and preserve required row-binlog scan columns without changing the scan output contract.

### Release note

Fix inconsistent storage-reader column mappings for projected and row-binlog scans.

### Check List (For Author)

- Test:
    - ASAN BE and FE build
    - BE clang-format and format check
    - FE Checkstyle
    - Regression: row_binlog_p0, delete_p0, unique_seq_map_p0, and targeted schema-change/delete/sequence suites
    - Regression: variant_p0 code-related cases passed; one outfile case was blocked by invalid external OSS credentials
- Behavior changed: Yes (reader column identity and row-binlog dependency handling are unified)
- Does this need documentation: No
csun5285 added a commit to csun5285/doris that referenced this pull request Aug 7, 2026
### What problem does this PR solve?

Issue Number: N/A

Related PR: apache#66432

Problem Summary: Storage readers maintained FE block positions, tablet-schema column IDs, predicate IDs, and delete-predicate columns through parallel mappings. Projection, nested-column pruning, virtual expressions, and row-binlog dependencies could make those mappings diverge. Use one ordered read schema as the reader coordinate, keep expected materialization types alongside physical columns, append storage-only delete-predicate dependencies after FE slots, and preserve required row-binlog scan columns without changing the scan output contract.

### Release note

Fix inconsistent storage-reader column mappings for projected and row-binlog scans.

### Check List (For Author)

- Test:
    - ASAN BE and FE build
    - BE clang-format and format check
    - FE Checkstyle
    - Regression: row_binlog_p0, delete_p0, unique_seq_map_p0, and targeted schema-change/delete/sequence suites
    - Regression: variant_p0 code-related cases passed; one outfile case was blocked by invalid external OSS credentials
- Behavior changed: Yes (reader column identity and row-binlog dependency handling are unified)
- Does this need documentation: No
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by one committer. dev/4.2.x

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug](High) BE crashes in VMergeIteratorContext::compare while initializing an OLAP scan

7 participants