risingwavelabs/risingwave · error

dist_key must in order_key

Error message

dist_key must in order_key

What it means

gen_batch_lookup_join computes the position of each distribution key column within the lookup table's order key and expect()s it to be found. If a distribution key column is not part of the primary-key/order columns, the invariant `dist_key must in order_key` is violated and it panics.

Solutions

  1. Ensure the lookup table's distribution key is a prefix/subset of its primary key (order key) columns
  2. Re-create the table with a PRIMARY KEY that matches its distribution
  3. Skip lookup-join conversion when the invariant does not hold and use a normal hash join
  4. Validate table.distribution_key ⊆ order_column_indices before calling

Example fix

// before
let pos = table.order_column_indices().position(|x| x == d).expect("dist_key must in order_key");
// after
let pos = match table.order_column_indices().position(|x| x == d) {
    Some(p) => p,
    None => return Err(anyhow!("distribution key {:?} not in order key of lookup table", d)),
};
Defensive patterns

Strategy: validation

Validate before calling

fn dist_key_in_order_key(table: &TableCatalog) -> bool { table.distribution_key.iter().all(|d| table.order_column_indices().any(|o| o == *d)) }

Prevention

When it happens

Trigger: Calling gen_batch_lookup_join (public) with a lookup table whose distribution key columns are not a subset of its order column indices.

Common situations: Lookup join against a table with a custom/non-default distribution or after schema changes where the distribution no longer aligns with the primary key; also hit by API users constructing lookup joins programmatically.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


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

Appendix: source

Thrown at src/frontend/src/optimizer/plan_node/logical_join.rs:408

            | JoinType::AsofInner
            | JoinType::AsofLeftOuter => {}
            _ => return Ok(None),
        };

        let table = logical_scan.table();
        let output_column_ids = logical_scan.output_column_ids();

        // Verify that the right join key columns are the the prefix of the primary key and
        // also contain the distribution key.
        let order_col_ids = table.order_column_ids();
        let dist_key = table.distribution_key.clone();
        // The at least prefix of order key that contains distribution key.
        let mut dist_key_in_order_key_pos = vec![];
        for d in dist_key {
            let pos = table
                .order_column_indices()
                .position(|x| x == d)
                .expect("dist_key must in order_key");
            dist_key_in_order_key_pos.push(pos);
        }
        // The shortest prefix of order key that contains distribution key.
        //
        // If the lookup table has a singleton distribution (i.e. an empty distribution key),
        // any non-empty prefix of the order key can be used for the lookup, so we require a
        // prefix of length at least 1.
        let shortest_prefix_len = dist_key_in_order_key_pos
            .iter()
            .max()
            .map_or(1, |pos| pos + 1);

        // Reorder the join equal predicate to match the order key.
        let reorder_idx =
            Self::lookup_prefix_reorder_idx(&predicate, &output_column_ids, &order_col_ids);
        if reorder_idx.len() < shortest_prefix_len {
            return Ok(None);
        }

View on GitHub (pinned to 6469eb736d)