#27126·risingwave

`IndexCost::le` is a strict `<` despite its name, and the cost-matrix doc drifted from the code

Author: yuhao-suCreated Sep 16, 2026Updated Sep 18, 2026
Labelstype/refactorA-optimizer

Two small maintainability issues in index_selection_rule.rs, noticed while reviewing #26317.

1. IndexCost::le is a strict <

rust
// index_selection_rule.rs:969
pub(crate) fn le(&self, other: &IndexCost) -> bool {
    self.cost < other.cost
}

The name reads as <=. The strictness is deliberate and load-bearing at all four call sites (three in select_index_access_path, one in streaming_index_selection_rule.rs), where min_cost is seeded with primary_cost:

  • a tie goes to the primary table, i.e. don't take an index unless it is strictly cheaper;
  • IndexCost::new clamps at maximum() = 10_000_000 and Default is also maximum() (returned whenever the estimator cannot analyse the predicate), so on a wide table — the no-predicate multiplier is 4000, so row size >= 2500 saturates — the primary and every index all land on exactly 10,000,000. Strict < is what makes the planner fall back to the base table there. With <= every index would tie at the ceiling and selection would degenerate into catalog order, with the cost model contributing nothing.

So the risk is that someone "fixes" the apparent typo and silently flips every tie, including the saturated case, churning a large number of plans.

Suggestion: rename to something unambiguous (strictly_cheaper_than), or collapse it into a single fn cmp_cost(&self, other: &Self) -> Ordering and let call sites match on the result. Note the struct currently carries three different comparison semantics: the derived Ord (lexicographic over cost and primary_lookup, used by min(...) and choose_min_cost_path's min_by), and le() (cost only).

2. The module doc's cost matrix drifted from the code

index_selection_rule.rs:23:

//! |All        | 4000| 100| 30 | 20 | 10 | |

but the code is:

rust
[4000, 100, 30, 20, 20],

(20, not 10, in the last position.)

And the worked example a few lines below:

//! - For `a = 1 and b in (xxx)`, its cost is Equal0 * In1 * All2 = 1 * 8 * 50 = 400

All2 is 30 per the matrix, so the product is 240, not 400.

Anyone hand-computing a cost from the doc will disagree with the planner.

Source: risingwavelabs/risingwave