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}