risingwavelabs/risingwave · error

lhs chunk cardinality should be 1 or 2

Error message

lhs chunk cardinality should be 1 or 2

What it means

`build_chunk` in the row-merge executor pairs one lhs row with one rhs row, so each chunk must have cardinality 1 or 2 (the executor's designed batching granularity). A lhs chunk with 0 or more than 2 rows violates the executor's batching contract and bails with this error.

Solutions

  1. Capture the actual lhs chunk cardinality (log `lhs_chunk.cardinality()`) and file an issue — this reflects broken internal batching invariants
  2. Recreate the streaming job as a workaround
  3. Check the RisingWave version for known row_merge batching regressions and upgrade
  4. When writing tests against row_merge, ensure test chunks are split to cardinality 1–2 before calling build_chunk

Example fix

// before (test wiring calling build_chunk directly with a big chunk)
Message::Chunk(big_chunk)
// after
for small_chunk in split_chunk(big_chunk, 2) { Message::Chunk(small_chunk) }
Defensive patterns

Strategy: try-catch

Try / catch

match build_result {
    Err(e) if e.to_string().contains("lhs chunk cardinality should be 1 or 2") => {
        // Batching invariant broken upstream: file an issue and recreate the job
        report_issue_with_chunk_trace();
        recreate_streaming_job(job_id);
    }
    other => other?,
}

Prevention

When it happens

Trigger: Internal: chunks delivered to `build_chunk` contain more than 2 rows (or are empty), meaning upstream buffering failed to split chunks to the expected 1–2 row size before merging.

Common situations: Changes in upstream chunking behavior feeding row_merge; bugs where a whole input chunk bypasses the split logic; misuse in tests/custom executor wiring.

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


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/3460ab101f086b17. Report an issue: GitHub.

Appendix: source

Thrown at src/stream/src/executor/row_merge.rs:150

                    data_types,
                    lhs_mapping,
                    rhs_mapping,
                    lhs_chunk.clone(),
                    rhs_chunk,
                )?;
            }
        }
    }

    fn build_chunk(
        data_types: &[DataType],
        lhs_mapping: &ColIndexMapping,
        rhs_mapping: &ColIndexMapping,
        lhs_chunk: StreamChunk,
        rhs_chunk: StreamChunk,
    ) -> Result<Message, StreamExecutorError> {
        if !(1..=2).contains(&lhs_chunk.cardinality()) {
            bail!("lhs chunk cardinality should be 1 or 2");
        }
        if !(1..=2).contains(&rhs_chunk.cardinality()) {
            bail!("rhs chunk cardinality should be 1 or 2");
        }
        if lhs_chunk.cardinality() != rhs_chunk.cardinality() {
            bail!("lhs and rhs chunk cardinality should be the same");
        }
        let cardinality = lhs_chunk.cardinality();
        let mut ops = Vec::with_capacity(cardinality);
        let mut merged_rows = vec![vec![Datum::None; data_types.len()]; cardinality];
        for (i, (op, lhs_row)) in lhs_chunk.rows().enumerate() {
            ops.push(op);
            for (j, d) in lhs_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) = lhs_mapping.try_map(j) {
                    merged_rows[i][out_index] = d.to_owned_datum();

View on GitHub (pinned to 6469eb736d)