risingwavelabs/risingwave · error

invalid backfill state: cdc_offset_low

Error message

invalid backfill state: cdc_offset_low

What it means

Thrown by CdcBackfillStateV2::restore_state when element [3] of the persisted state row is neither a Jsonb-encoded CDC offset nor null, so cdc_offset_low cannot be deserialized. The code accepts Some(Jsonb) and None; any other scalar is rejected.

Source

Thrown at src/stream/src/executor/backfill/cdc/state_v2.rs:82

        {
            Some(row) => {
                tracing::info!("restored cdc backfill state: {:?}", row);
                let state = row.into_inner().into_vec();
                let is_finished = match state[1] {
                    Some(ScalarImpl::Bool(val)) => val,
                    _ => return Err(anyhow!("invalid backfill state: backfill_finished").into()),
                };
                let row_count = match state[2] {
                    Some(ScalarImpl::Int64(val)) => val,
                    _ => return Err(anyhow!("invalid backfill state: row_count").into()),
                };
                let (cdc_offset_low, cdc_offset_high) = if !self.is_legacy_state {
                    let cdc_offset_low = match state[3] {
                        Some(ScalarImpl::Jsonb(ref jsonb)) => {
                            serde_json::from_value(jsonb.clone().take()).unwrap()
                        }
                        None => None,
                        _ => return Err(anyhow!("invalid backfill state: cdc_offset_low").into()),
                    };
                    let cdc_offset_high = match state[4] {
                        Some(ScalarImpl::Jsonb(ref jsonb)) => {
                            serde_json::from_value(jsonb.clone().take()).unwrap()
                        }
                        None => None,
                        _ => return Err(anyhow!("invalid backfill state: cdc_offset_high").into()),
                    };
                    (cdc_offset_low, cdc_offset_high)
                } else {
                    (None, None)
                };

                Ok(CdcStateRecord {
                    is_finished,
                    row_count,
                    cdc_offset_low,
                    cdc_offset_high,

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Cancel and recreate the streaming job so the backfill state is rebuilt
  2. Ensure all nodes run the same RisingWave version (rolling upgrades must not mix old/new state encoders for the same job)
  3. Inspect the internal state table row to confirm the offset field type
  4. Drop and recreate the CDC table/MV from the upstream source if state is unusable
Defensive patterns

Strategy: validation

Validate before calling

let low_ok = matches!(state[STATE_OFFSET_LOW_IDX], None | Some(ScalarImpl::Jsonb(_)));
if !low_ok { /* state written by incompatible encoder; recreate */ }

Prevention

When it happens

Trigger: Restoring a non-legacy v2 CDC backfill state where state[3] holds a scalar other than Jsonb/None, e.g. a Bytea offset from a legacy layout or a partially-written row.

Common situations: Mixed-version clusters where the state writer and reader disagree on offset encoding; migration bugs; corrupted state after crash during state update.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


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