Repository navigation
Description
Search before asking
- I searched the existing issues. [Bug] VMergeIteratorContext::compare core dump #16495 reports a crash in the same comparator, but it is closed and its stack reaches
VMergeIteratorContext::compare()from_next_batch(). The crash reported here occurs whileVMergeIterator::init()is building its heap on a current master commit.
Version
Reproduced on Apache Doris commit 1590e7e3228f8f0cafcaa89e841fece09c2475be in a non-cloud deployment with 1 FE and 3 BEs.
What's Wrong?
During a ROW binlog regression workload, a valid query caused a BE to receive SIGSEGV. The first Doris business frame is VMergeIteratorContext::compare() at src/storage/iterator/vgeneric_iterators.cpp:128.
The comparator is called by std::push_heap() while VMergeIterator::init() initializes rowset readers. The crash terminates the BE instead of returning a query error.
What You Expected?
A valid OLAP scan should initialize its rowset merge iterators and complete normally. If a rowset or iterator state is invalid, Doris should return a diagnostic error instead of dereferencing invalid memory and terminating the BE process.
How to Reproduce?
The crash was observed during a ROW binlog regression workload on a 1 FE / 3 BE cluster. The original SQL could not be recovered from the retained audit/profile records, so a standalone deterministic reproduction is not available yet.
The current reproduction gap is to recover or minimize the query and identify the exact rowset/segment combination passed to VMergeIterator::init().
Regression Test Result
Sanitized continuous stack:
doris::signal::FailureSignalHandler(...)
libc.so.6
doris::VMergeIteratorContext::compare(...) const
at src/storage/iterator/vgeneric_iterators.cpp:128
std::__push_heap(...)
at bits/stl_heap.h:140
std::push_heap(...)
doris::VMergeIterator::init(...)
at src/storage/iterator/vgeneric_iterators.cpp:373
doris::BetaRowsetReader::_init_iterator()
at src/storage/rowset/beta_rowset_reader.cpp:407
doris::BetaRowsetReader::_init_iterator_once()
at src/storage/rowset/beta_rowset_reader.cpp:368
doris::BetaRowsetReader::_next_batch<doris::BlockWithSameBit>(...)
at src/storage/rowset/beta_rowset_reader.h:102
doris::BetaRowsetReader::next_batch(...)
at src/storage/rowset/beta_rowset_reader.h:59
doris::VCollectIterator::Level0Iterator::refresh_current_row()
at src/storage/iterator/vcollect_iterator.cpp:537
doris::VCollectIterator::Level0Iterator::init(bool)
at src/storage/iterator/vcollect_iterator.cpp:489
doris::VCollectIterator::build_heap(...)
at src/storage/iterator/vcollect_iterator.cpp:142
doris::BlockReader::_init_collect_iter(...)
at src/storage/iterator/block_reader.cpp:481
doris::BlockReader::init(...)
at src/storage/iterator/block_reader.cpp:601
doris::OlapScanner::_open_impl(...)
at src/exec/scan/olap_scanner.cpp:316
doris::Scanner::open(...)
doris::ScannerScheduler::_scanner_scan(...)
doris::ScannerSplitRunner::process_for(...)
at src/exec/scan/scanner_scheduler.cpp:384
doris::PrioritizedSplitRunner::process()
doris::TimeSharingTaskExecutor::_dispatch_thread()
at src/exec/scan/task_executor/time_sharing/time_sharing_task_executor.cpp:574
doris::Thread::supervise_thread(void*)
at src/util/thread.cpp:461
start_thread
clone3
Initial Analysis
Confirmed:
- The direct crash point is
VMergeIteratorContext::compare(). - The crash happens during heap construction in
VMergeIterator::init(), before the scanner starts returning normal batches. - The running BE commit matches the public Apache Doris commit listed above.
- This differs from [Bug] VMergeIteratorContext::compare core dump #16495, where the comparator was reached from
_next_batch().
Suspected:
- One merge context may contain an invalid current-row reference, comparison column, or underlying column object when the heap comparator runs.
- The lifetime and initialization state of the contexts inserted into the heap should be checked, especially after each rowset reader performs its first batch load.
Anything Else?
Tracking issue: #65265
Related historical issue: #16495
Additional evidence still needed:
- A minimized SQL/DDL/data reproduction.
- The exact rowset and segment metadata involved in the failing comparison.
- Core-variable inspection for both comparator operands at the crash frame.
Are you willing to submit PR?
- Yes I am willing to submit a PR!
Activity
Doris-Breakwater commentedon Aug 3, 2026
Initial maintainer analysis
Triage: This is a credible high-severity BE crash. The issue currently has no labels;
bug,BE/storage, and the project's high-priority label would be appropriate. I found a concrete code-level crash condition at the reported commit that closely matches the stack, but the missing SQL/core operands prevent attributing this particular incident with certainty.Verified code facts
VMergeIterator::init()initializes each context, skips invalid/EOF contexts, and then pushes ashared_ptrinto the heap. This makes a simple context-lifetime or "uninitialized context was pushed" explanation unlikely: vgeneric_iterators.cpp#L294-L303, vgeneric_iterators.cpp#L357-L373._compare_columnsremains null, so the comparator takes the exact line-128 branch and compares_num_key_columns.VMergeIteratorContext::_num_key_columnscomes from_output_schema->num_key_columns(): vgeneric_iterators.h#L86-L98. However,Schema(columns, col_ids)counts key columns across the entire tablet schema, not across the projectedcol_ids: schema.h#L51-L81.MIN_DELTA/DETAIL, the scanner explicitly widens the storage projection with all key columns. For other binlog modes, notablyAPPEND_ONLY, thedirect_modebranch can leavereturn_columnsas only the SQL projection: olap_scanner.cpp#L471-L525. This is reachable for the normal no-storage-merge DUP/UNIQUE-MOW path.Block::compare_at()only hasDCHECKbounds checks and then uses unchecked positional access. In a release BE,_num_key_columns > block.columns()can therefore dereference past the block's column array instead of returning aStatus: block.h#L375-L385.Leading root-cause hypothesis (strong, not incident-confirmed)
An
APPEND_ONLYROW-binlog query projected fewer columns than the tablet's key count, or projected columns without all leading keys. The output block was built from that projection, while the merge comparator retained the full tablet key count. Once a rowset with at least two segments was initialized, the second heap insertion calledcompare()and read a missing/wrong positional column.This explains all distinctive evidence in the report:
VMergeIterator::init()/std::push_heap()rather than_next_batch();SIGSEGVrather than a diagnostic error.Even when
block.columns() >= _num_key_columns, a projection that omits/reorders keys can make the comparator sort by value columns, so this is also a correctness invariant issue, not only an out-of-bounds issue. This mechanism is different from #16495, whose fix corrected a sequence-column schema-ID/block-position mismatch.Evidence still needed to confirm this crash
incrementTypeand projected columns, plusEXPLAIN VERBOSE.SHOW CREATE TABLE/full schema, including key type, key columns, ROW-binlog properties, and hidden binlog columns._valid,_index_in_block,_block,_block->rows(),_block->columns(),_num_key_columns,_compare_columns,_sequence_id_idx,_output_schema->column_ids(), and_output_schema->num_column_ids(). The decisive check is whether either block has fewer columns than_num_key_columns, or whether its first_num_key_columnspositions are not the full ordered key prefix.Recommended next steps
APPEND_ONLYquery that omits one or all key columns and a rowset forced to contain at least two segments. Cover both DUP and UNIQUE-MOW where practical.APPEND_ONLYwith a key-omitting projection and a multi-segment rowset.Breakwater-GitHub-Analysis-Slot: slot_2156593f0d29
[fix](binlog) Fix BE crash when APPEND_ONLY row-binlog scan omits key…
[fix](binlog) Fix BE crash when APPEND_ONLY row-binlog scan omits key…
[refactor](storage) Shape the AGG_KEYS heap merge and fold like StarR…
[refactor](storage) Merge the rows of an AGG_KEYS key in write order …
[refactor](storage) Name the heap merge's helpers for what they do an…