risingwavelabs/risingwave · error

invalid backfill state: unfinished row has null cdc_offset

Error message

invalid backfill state: unfinished row has null cdc_offset

What it means

The CDC backfill state row's final column is `cdc_offset`, stored as Jsonb. A NULL offset is only legal when `backfill_finished` is true (no offset needed). If the offset is NULL while the backfill is unfinished, the executor cannot resume from a position, so `restore_state` returns this error; a non-Jsonb, non-NULL value yields the sibling 'invalid backfill state: cdc_offset' error.

Source

Thrown at src/stream/src/executor/backfill/cdc/state.rs:86

                self.cached_state = row.into_inner().into_vec();
                let state = self.cached_state.as_slice();
                let state_len = state.len();
                // schema: | `split_id` | `pk...` | `backfill_finished` | `row_count` | `cdc_offset` |
                let row_count = match state[state_len - 2] {
                    Some(ScalarImpl::Int64(val)) => val,
                    _ => return Err(anyhow!("invalid backfill state: row_count").into()),
                };
                let is_finished = match state[state_len - 3] {
                    Some(ScalarImpl::Bool(val)) => val,
                    _ => return Err(anyhow!("invalid backfill state: backfill_finished").into()),
                };
                let cdc_offset = match state[state_len - 1] {
                    Some(ScalarImpl::Jsonb(ref jsonb)) => {
                        serde_json::from_value(jsonb.clone().take()).unwrap()
                    }
                    None if is_finished => None,
                    None => {
                        return Err(anyhow!(
                            "invalid backfill state: unfinished row has null cdc_offset"
                        )
                        .into());
                    }
                    _ => return Err(anyhow!("invalid backfill state: cdc_offset").into()),
                };

                let current_pk_pos = state[1..state_len - 3].to_vec();
                Ok(CdcStateRecord {
                    current_pk_pos: Some(OwnedRow::new(current_pk_pos)),
                    is_finished,
                    last_cdc_offset: cdc_offset,
                    row_count,
                })
            }
            None => Ok(CdcStateRecord::default()),
        }
    }

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Drop and re-create the CDC table / materialized view using CDC backfill to restart backfill from the snapshot.
  2. Verify no crash occurred mid state-commit (check earlier logs); if so, rebuilding state is the safe path.
  3. Check RisingWave version skew between writer and reader; rebuild state after upgrading.
  4. Inspect the state table row to confirm cdc_offset is NULL and report as a writer bug if reproducible.
Defensive patterns

Strategy: fallback

Prevention

When it happens

Trigger: Recovery of an unfinished CDC backfill whose persisted state row has a NULL `cdc_offset` — state row written without the offset (e.g. partial write or writer bug) while `backfill_finished` is false.

Common situations: Crash between writing `backfill_finished=false` state and persisting the offset, version upgrades altering the encoding, or corrupted state store rows.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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