Repository navigation
Description
Tracking
- Tracking issue: [Tracking][Binlog] Binlog feature umbrella — all sub-issues tracked here #65265
- Internal reference: DORIS-27318
Search before asking
- I searched the existing issues and found no issue with the same assertion or iterator-mode conflict.
Version
Reproduced on Doris commit 87d57a84729.
What's Wrong?
Querying an APPEND_ONLY table stream backed by ROW binlog can trigger a fatal assertion in the BE scan path when a data rowset contains multiple segments:
Check failed: !_get_data_by_ref
The BE then receives SIGABRT and exits.
The outer BlockReader sees a NONOVERLAPPING rowset and selects Block-based batch reading. ROW binlog requires stable ordered output, so the inner VCollectIterator selects merge/RowRef reading when the rowset has multiple segments. These independently selected modes conflict: the child iterator remains in by-reference mode while the parent calls the Block interface, causing the assertion.
The assertion prevents the engine from continuing in an inconsistent state that could otherwise produce missing, duplicated, or incorrect rows.
What You Expected?
The table stream query should complete without crashing the BE. BlockReader and all child VCollectIterator instances should use a consistent Block or RowRef mode while preserving ROW binlog ordering semantics.
How to Reproduce?
- Create a table with ROW binlog enabled.
- Create an
APPEND_ONLYtable stream on the table. - Write enough data for a data rowset to contain multiple segments, with the rowset classified as
NONOVERLAPPING. - Query the table stream.
- Observe the BE assertion in
VCollectIterator::Level0Iterator::next(Block*)followed bySIGABRT.
The issue does not reproduce when the data rowset has only one segment because the inner iterator does not enter merge/RowRef mode.
be.out
The following excerpt is limited to the relevant, sanitized be.out content:
Check failed: !_get_data_by_ref
*** Current BE git commitID: 87d57a84729 ***
*** SIGABRT received; stack trace: ***
0# doris::signal::FailureSignalHandler(int, siginfo_t*, void*)
1# pthread_kill
2# raise
3# abort
4# google::LogMessage::SendToLog()
5# google::LogMessage::Flush()
6# google::LogMessageFatal::~LogMessageFatal()
7# doris::VCollectIterator::Level0Iterator::next(doris::Block*)
8# doris::VCollectIterator::Level1Iterator::_normal_next(doris::Block*)
9# doris::VCollectIterator::Level1Iterator::next(doris::Block*)
10# doris::VCollectIterator::next(doris::Block*)
11# doris::BlockReader::_direct_next_block(doris::Block*, bool*)
12# doris::BlockReader::next_block_with_aggregation(doris::Block*, bool*)
13# doris::OlapScanner::_get_block_impl(doris::RuntimeState*, doris::Block*, bool*)
14# doris::Scanner::get_block(doris::RuntimeState*, doris::Block*, bool*)
15# doris::Scanner::get_block_after_projects(doris::RuntimeState*, doris::Block*, bool*)
16# doris::ScannerScheduler::_scanner_scan(...)
17# doris::ScannerSplitRunner::process_for(...)
18# doris::PrioritizedSplitRunner::process()
19# doris::TimeSharingTaskExecutor::_dispatch_thread()
20# doris::Thread::supervise_thread(void*)
Root Cause
ROW binlog is the direct trigger, but the code-level defect is inconsistent mode selection between BlockReader and VCollectIterator. The parent must explicitly propagate whether Block or RowRef output is required, and Block mode must prevent every child iterator from entering by-reference mode.
Are you willing to submit PR?
- Yes, I am willing to submit a PR!
Activity
zclllyybb commentedon Jul 22, 2026
Breakwater-GitHub-Analysis-Slot: slot_c3914a919850
This content is generated by AI for reference only.
Initial assessment: this looks like a real BE storage scan bug, not a resource issue or an invalid SQL/table-stream usage. The reported stack is consistent with a caller/callee mode contract violation: the outer scan path calls the Block interface while a child rowset iterator has already been initialized for row-reference output.
Code-path check on commit
87d57a84729:APPEND_ONLYtable stream is propagated asTBinlogScanType::APPEND_ONLY.OlapScannertreats any row-binlog scan as ordered: it setsread_orderby_key = trueandforce_key_ordered_read = true.TabletReader::ReaderParams::has_single_version()can make the scan usedirect_mode = true;BlockReader::init()only divertsMIN_DELTAandDETAILto row-by-row binlog-specific readers, soAPPEND_ONLYcan still use_direct_next_block()and callVCollectIterator::next(Block*).BetaRowsetReader::is_merge_iterator()returns true when ordered output is required, the rowset has more than one segment, andforce_key_ordered_readis set. Therefore a multi-segment ROW-binlog rowset can become an inner merge iterator even when its rowset metadata isNONOVERLAPPING.Level0Iteratorcan be initialized withget_data_by_ref = true. If an earlier child is empty and skipped, that by-reference child can become the current child while the parent still uses the Block interface.VCollectIterator::Level0Iterator::next(Block*)then hitsCHECK(!_get_data_by_ref), which matches the reported abort.So the high-confidence root cause is an inconsistent read-mode contract between
BlockReader/VCollectIteratorand the rowset reader under APPEND_ONLY ROW-binlog scans. The fatal check should not be removed; it is protecting against continuing after the storage layer has mixed two incompatible output protocols.Recommended fix direction:
BlockReaderdown to everyVCollectIterator::Level0Iterator._get_data_by_refmode, while still preserving the ordered multi-segment row-binlog merge insideBetaRowsetReader.MIN_DELTA/DETAILreaders, but without changing APPEND_ONLY semantics.NONOVERLAPPINGdata rowset, including the empty-initial-rowset plus data-rowset shape if that is the actual failing layout, then queries the APPEND_ONLY stream and verifies both no BE abort and stable ordered output.Useful missing information for a self-contained upstream repro:
segments_overlap, and whether an empty rowset precedes the data rowset.read_row_binlog=trueandbinlog_scan_type=APPEND_ONLY.zclllyybb commentedon Jul 22, 2026
Breakwater-GitHub-Analysis-Slot: slot_c3914a919850
This content is generated by AI for reference only.
Initial assessment: this looks like a real BE storage scan bug, not a resource issue or an invalid SQL/table-stream usage. The reported stack is consistent with a caller/callee mode contract violation: the outer scan path calls the Block interface while a child rowset iterator has already been initialized for row-reference output.
Code-path check on commit
87d57a84729:APPEND_ONLYtable stream is propagated asTBinlogScanType::APPEND_ONLY.OlapScannertreats any row-binlog scan as ordered: it setsread_orderby_key = trueandforce_key_ordered_read = true.TabletReader::ReaderParams::has_single_version()can make the scan usedirect_mode = true;BlockReader::init()only divertsMIN_DELTAandDETAILto row-by-row binlog-specific readers, soAPPEND_ONLYcan still use_direct_next_block()and callVCollectIterator::next(Block*).BetaRowsetReader::is_merge_iterator()returns true when ordered output is required, the rowset has more than one segment, andforce_key_ordered_readis set. Therefore a multi-segment ROW-binlog rowset can become an inner merge iterator even when its rowset metadata isNONOVERLAPPING.Level0Iteratorcan be initialized withget_data_by_ref = true. If an earlier child is empty and skipped, that by-reference child can become the current child while the parent still uses the Block interface.VCollectIterator::Level0Iterator::next(Block*)then hitsCHECK(!_get_data_by_ref), which matches the reported abort.So the high-confidence root cause is an inconsistent read-mode contract between
BlockReader/VCollectIteratorand the rowset reader under APPEND_ONLY ROW-binlog scans. The fatal check should not be removed; it is protecting against continuing after the storage layer has mixed two incompatible output protocols.Recommended fix direction:
BlockReaderdown to everyVCollectIterator::Level0Iterator._get_data_by_refmode, while still preserving the ordered multi-segment row-binlog merge insideBetaRowsetReader.MIN_DELTA/DETAILreaders, but without changing APPEND_ONLY semantics.NONOVERLAPPINGdata rowset, including the empty-initial-rowset plus data-rowset shape if that is the actual failing layout, then queries the APPEND_ONLY stream and verifies both no BE abort and stable ordered output.Useful missing information for a self-contained upstream repro:
segments_overlap, and whether an empty rowset precedes the data rowset.read_row_binlog=trueandbinlog_scan_type=APPEND_ONLY.[fix](table stream) Fix multi-segment row-binlog reads for MoW tables (…