risingwavelabs/risingwave · error
invalid backfill state: backfill_finished
Error message
invalid backfill state: backfill_finished
What it means
Thrown by CdcBackfillStateV2::restore_state when element [1] of the persisted state row is not a Bool, so the backfill_finished flag cannot be decoded. It guards against reading a state row written with a different layout or corrupted value.
Source
Thrown at src/stream/src/executor/backfill/cdc/state_v2.rs:70
pub async fn init_epoch(&mut self, epoch: EpochPair) -> StreamExecutorResult<()> {
self.state_table.init_epoch(epoch).await
}
/// Restore the backfill state from storage
pub async fn restore_state(&mut self, split_id: i64) -> StreamExecutorResult<CdcStateRecord> {
let key = Some(split_id);
match self
.state_table
.get_row(row::once(key.map(ScalarImpl::from)))
.await?
{
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,View on GitHub (pinned to 6469eb736d)
Solutions
- Cancel and recreate the streaming job so the CDC backfill is rebuilt with a fresh state row
- Verify the writer/reader versions match; downgrade to the version that wrote the state and let backfill finish before upgrading
- Inspect the internal state table row to confirm the field type mismatch
- Restore the state table from backup if the row was corrupted
Defensive patterns
Strategy: validation
Validate before calling
let is_finished = matches!(state[STATE_FINISHED_IDX], Some(ScalarImpl::Bool(_)));
if !is_finished { /* treat state as incompatible; recreate job */ } Prevention
- Upgrade via the documented migration path instead of skipping versions
- Back up state tables before major version upgrades
- Watch migration test suites for state-layout changes
When it happens
Trigger: Restoring a v2 CDC backfill state where state[1] is None or holds a non-Bool ScalarImpl (e.g. Int64/Bytea from a legacy or foreign row layout).
Common situations: Version upgrade/migration where the state schema changed; a row written by the legacy no-shuffle backfill being read by the v2 state reader; corrupted persisted state after a crash mid-write.
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
- invalid backfill state: cdc_offset
- invalid backfill state: row_count
- invalid backfill state: cdc_offset_low
- invalid backfill state: cdc_offset_high
- next offset {:?} should be later than current offset {:?}
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/bdb37142eb44d716.
Report an issue: GitHub.