risingwavelabs/risingwave · error
builder should have yielded a chunk
Error message
builder should have yielded a chunk
What it means
RowMergeExecutor's build_chunk merges rows from a source and upsert batch, buffering them into a DataChunkBuilder sized to the expected cardinality. After appending all merged rows it asserts the builder has already flushed a chunk during iteration; if no chunk was produced the merge produced fewer rows than expected, so the executor treats this as an internal invariant violation and bails.
Solutions
- Check the merge logic in RowMergeExecutor::execute_inner to confirm the merged_rows iterator cannot be empty when build_chunk is called
- Verify sort key columns match between the source stream and the upsert state table; mismatched keys yield no merged rows
- Upgrade or bisect RisingWave versions — this bail typically indicates an executor bug; file an issue with the actor_id and plan if reproducible
- Check for recent recovery/failover events that may have left the state table inconsistent with the source log
Defensive patterns
Strategy: try-catch
Try / catch
// In executor error handling, treat as non-recoverable internal error and log with actor context
match exec_result {
Err(e) if e.to_string().contains("builder should have yielded a chunk") => {
tracing::error!(actor = %actor_id, "row merge invariant violated, forcing recovery");
Err(e) // bubble up to trigger recovery, do not retry locally
}
other => other,
} Prevention
- Add unit tests covering empty and undersized merged row iterators
- Assert cardinality assumptions next to DataChunkBuilder::new construction
- Keep sort-key column selection for source and state in one shared helper to avoid drift
When it happens
Trigger: The merged row iterator (source rows merged with the upsert-state rows) is empty or yields fewer rows than the cardinality passed to DataChunkBuilder::new, so builder.append_one_row never returns Some(chunk) during the loop.
Common situations: Internal bugs in merge logic (mismatched sort keys between source and state tables), upstream executor changes that change cardinality assumptions, or corrupt stream state after failover where the source log contains no matching rows.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- BatchPosixFsReader should not be used
- BatchPosixFsReader should not hit this branch. refer to…
- Can't create deltalake sink write result from empty data!
- Expected RexNode::FuncCall
- iceberg pk-index writer
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/8a4ad009fb2ad50b.
Report an issue: GitHub.
Appendix: source
Thrown at src/stream/src/executor/row_merge.rs:189
}
for (i, (_, rhs_row)) in rhs_chunk.rows().enumerate() {
for (j, d) in rhs_row.iter().enumerate() {
// NOTE(kwannoel): Unnecessary columns will not have a mapping,
// for instance extra row count column.
// those can be skipped here.
if let Some(out_index) = rhs_mapping.try_map(j) {
merged_rows[i][out_index] = d.to_owned_datum();
}
}
}
let mut builder = DataChunkBuilder::new(data_types.to_vec(), cardinality);
for row in merged_rows {
if let Some(chunk) = builder.append_one_row(&row[..]) {
return Ok(Message::Chunk(StreamChunk::from_parts(ops, chunk)));
}
}
bail!("builder should have yielded a chunk")
}
}
impl Execute for RowMergeExecutor {
fn execute(self: Box<Self>) -> BoxedMessageStream {
self.execute_inner().boxed()
}
}
View on GitHub (pinned to 6469eb736d)