mz_transform/notice/
index_key_empty.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! Hosts [`IndexKeyEmpty`].
11
12use std::collections::BTreeSet;
13use std::fmt;
14
15use mz_repr::GlobalId;
16use mz_repr::explain::ExprHumanizer;
17
18use crate::notice::{ActionKind, OptimizerNoticeApi};
19
20/// An index with an empty key is maximally skewed (all of the data goes to a single worker),
21/// and is almost never really useful. It's slightly useful for a cross join, because a cross
22/// join also has an empty key, so we avoid rearranging the input. However, this is still
23/// not very useful, because
24///  - Rearranging the input shouldn't take too much memory, because if a cross join has a big
25///    input, then we have a serious problem anyway.
26///  - Even with the arrangement already there, the cross join will read every input record, so
27///    the orders of magnitude performance improvements that can happen with other joins when an
28///    input arrangement exists can't happen with a cross join.
29/// Also note that skew is hard to debug, so it's good to avoid this problem in the first place.
30#[derive(Clone, Debug, Eq, PartialEq, Hash)]
31pub struct IndexKeyEmpty;
32
33impl OptimizerNoticeApi for IndexKeyEmpty {
34    fn dependencies(&self) -> BTreeSet<GlobalId> {
35        BTreeSet::new()
36    }
37
38    fn fmt_message(
39        &self,
40        f: &mut fmt::Formatter<'_>,
41        _humanizer: &dyn ExprHumanizer,
42        _redacted: bool,
43    ) -> fmt::Result {
44        write!(
45            f,
46            "Empty index key. \
47            The index will be completely skewed to one worker thread, \
48            which can lead to performance problems."
49        )
50    }
51
52    fn fmt_hint(
53        &self,
54        f: &mut fmt::Formatter<'_>,
55        _humanizer: &dyn ExprHumanizer,
56        _redacted: bool,
57    ) -> fmt::Result {
58        write!(
59            f,
60            "CREATE DEFAULT INDEX is almost always better than an index with an empty key. \
61            (Except for cross joins with big inputs, which are better to avoid anyway.)"
62        )
63    }
64
65    fn fmt_action(
66        &self,
67        f: &mut fmt::Formatter<'_>,
68        _humanizer: &dyn ExprHumanizer,
69        _redacted: bool,
70    ) -> fmt::Result {
71        write!(
72            f,
73            "Drop the enclosing index and re-create it using `CREATE DEFAULT INDEX ON` instead."
74        )
75    }
76
77    fn action_kind(&self, _humanizer: &dyn ExprHumanizer) -> ActionKind {
78        ActionKind::PlainText
79    }
80}