risingwavelabs/risingwave · error
Expected at most 1 clean_watermark_index per table, got {:?}
Error message
Expected at most 1 clean_watermark_index per table, got {:?} What it means
In stream HashJoin watermark inference, each table may have at most one column used for state cleaning (clean_watermark_index). If the collected `clean_watermark_indices` vector has more than one entry, `infer_clean_watermark_indices` returns this error via `bail!`. The invariant is supposed to be guaranteed upstream by `derive_watermark_for_hash_join`, so this error means the join's inequality/equality conditions yielded multiple cleanable columns per table.
Source
Thrown at src/frontend/src/optimizer/plan_node/stream_hash_join.rs:486
if is_left && *clean_left {
let col_idx = pair.left_idx;
if !clean_watermark_indices.contains(&col_idx) {
inequal_clean_watermark_indices.push(col_idx);
}
} else if !is_left && *clean_right {
let col_idx = pair.right_idx;
if !clean_watermark_indices.contains(&col_idx) {
inequal_clean_watermark_indices.push(col_idx);
}
}
}
clean_watermark_indices.extend(inequal_clean_watermark_indices.clone());
// Verify: only 1 column per table is allowed to do state cleaning.
// This invariant is enforced by `derive_watermark_for_hash_join`.
if clean_watermark_indices.len() > 1 {
bail!(
"Expected at most 1 clean_watermark_index per table, got {:?}",
clean_watermark_indices
)
}
Ok((
clean_watermark_indices,
eq_join_key_clean_watermark_indices,
inequal_clean_watermark_indices,
))
}
/// Infer which join keys can be used for state cleaning based on equal conditions.
fn infer_eq_join_key_clean_watermark_indices(&self, join_key_indices: &[usize]) -> Vec<usize> {
let mut clean_indices = vec![];
for (idx_in_jk, do_state_cleaning) in &self.watermark_indices_in_jk {
if *do_state_cleaning {
let col_idx = join_key_indices[*idx_in_jk];View on GitHub (pinned to 6469eb736d)
Solutions
- Rewrite the query so each joined table has at most one inequality/watermark-cleaning condition (drop or merge extra non-equi predicates).
- Check `derive_watermark_for_hash_join` output for the failing join to see which conditions produced multiple indices.
- If the invariant should hold, fix the derivation logic so it enforces at most one cleaning column per table before inference runs.
- Simplify the join (e.g. pre-filter inputs or split the join) to avoid multiple cleanable columns.
Example fix
-- before: multiple cleaning columns per table CREATE MV AS SELECT * FROM t1 JOIN t2 ON t1.ts > t2.ts AND t1.id > t2.id; -- after: keep only one inequality suitable for watermark cleaning CREATE MV AS SELECT * FROM t1 JOIN t2 ON t1.ts > t2.ts AND t1.id = t2.id;
Defensive patterns
Strategy: validation
Validate before calling
-- SQL: ensure at most one non-equi (watermark-cleaning) condition per joined table -- Inspect the join predicates before creating the MV: -- SELECT pg_get_expr(ev.ev, ev.oid) FROM ... -- or review the ON clause manually -- Count inequality predicates per table; rewrite if > 1 per side.
Try / catch
// Rust caller: treat bail! result as an error, not panic
match node.infer_clean_watermark_indices() {
Ok(indices) => proceed(indices),
Err(e) => return Err(context!(e, "hash join watermark inference")),
} Prevention
- Keep at most one inequality predicate per table in streaming join conditions.
- Convert additional non-equi conditions into equality or pre-filters.
- After upgrading, re-derive watermarks with EXPLAIN to check cleaning columns.
- Add planner tests for joins with multiple inequality conditions.
When it happens
Trigger: Creating a `CREATE MATERIALIZED VIEW` with a stream hash join whose watermark derivation produces more than one clean_watermark_index for one side of the join, i.e. multiple inequality conditions each mapping to cleanable columns on the same table, when `derive_watermark_for_hash_join` did not collapse them.
Common situations: Queries with multiple non-equi join conditions on watermark columns; upgrading RisingWave after changes to watermark/now-offset derivation rules; writing SQL that combines several inequality predicates (e.g. `t1.ts > t2.ts AND t1.id > t2.id`) on streaming joins with watermarks.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- ALTER TABLE ALTER WATERMARK is not supported for iceberg tab
- Scalar subquery might produce more than one row.
- inequal condition from the same side should be push down in
- should always have a stream key in the stream plan but not,
- update should always be converted to batch plan
AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11).
Data as JSON: /api/errors/9df6f68cf8d047d3.
Report an issue: GitHub.