risingwavelabs/risingwave · error · StreamExecutorError

Watermark serde should have at least one order type

Error message

Watermark serde should have at least one order type

What it means

After confirming the watermark value is non-NULL, the state table reads the watermark serde's order types to determine ascending/descending direction. This error fires when `watermark_serde.get_order_types()` returns an empty vector, meaning the serde was constructed without any ordering information, so the comparison direction cannot be computed.

Source

Thrown at src/stream/src/common/table/state_table.rs:2206

                .await
                .map(|s| s.boxed());
        };
        let watermark_row = watermark_serde.deserialize(&watermark_bytes)?;
        if watermark_row.len() != 1 {
            return Err(StreamExecutorError::from(format!(
                "Watermark row should have exactly 1 column, got {}",
                watermark_row.len()
            )));
        }
        let watermark_value = watermark_row[0].clone();
        // StateTableInner::update_watermark should ensure that the watermark is not NULL
        if watermark_value.is_none() {
            return Err(StreamExecutorError::from(anyhow!(
                "Watermark cannot be NULL"
            )));
        }
        let order_type = watermark_serde.get_order_types().get(0).ok_or_else(|| {
            StreamExecutorError::from(anyhow!(
                "Watermark serde should have at least one order type"
            ))
        })?;

        let direction = if order_type.is_ascending() {
            WatermarkDirection::Ascending
        } else {
            WatermarkDirection::Descending
        };
        let clean_watermark_index_in_pk = self
            .pk_indices
            .iter()
            .position(|&i| i == clean_watermark_index);
        let clean_watermark_index_in_value = match &self.value_indices {
            Some(value_indices) => value_indices
                .iter()
                .position(|idx| *idx == clean_watermark_index)
                .ok_or_else(|| {

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Inspect where the WatermarkSerde is constructed and ensure it is built with the watermark column's order type
  2. Verify the stream plan fragment includes the sort/order type for the watermark column
  3. Rebuild/upgrade to a version where serde construction matches the state table API
  4. If persistent, log the full order_types vector at serde build time to confirm it is empty at creation

Example fix

// before
let serde = WatermarkSerde::new(vec![], data_types);
// after
let serde = WatermarkSerde::new(vec![OrderType::Ascending], data_types);
Defensive patterns

Strategy: validation

Validate before calling

fn check_serde(serde: &WatermarkSerde) -> Result<(), StreamExecutorError> {
    if serde.get_order_types().is_empty() {
        Err(StreamExecutorError::from(anyhow!("watermark serde has no order types")))
    } else { Ok(()) }
}

Type guard

fn serde_has_order_type(serde: &WatermarkSerde) -> bool { !serde.get_order_types().is_empty() }

Prevention

When it happens

Trigger: Calling the state table watermark update path with a WatermarkSerde built from an empty order-types slice — typically a serde misconstructed in executor/table initialization.

Common situations: Incorrect column list passed when building the serde during fragment/graph dispatch; a refactor or version change that dropped the order type from serde construction; malformed stream plan with no sort column on the watermark key.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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