Skip to content

Commit ada3134

Browse files
HIVE-29574: Merge join skew monitor (#6456)
1 parent 8876c17 commit ada3134

22 files changed

Lines changed: 2327 additions & 1 deletion

common/src/java/org/apache/hadoop/hive/conf/HiveConf.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1737,6 +1737,15 @@ public static enum ConfVars {
17371737
"How many rows in the right-most join operand Hive should buffer before emitting the join result."),
17381738
HIVE_JOIN_CACHE_SIZE("hive.join.cache.size", 25000,
17391739
"How many rows in the joining tables (except the streaming table) should be cached in memory."),
1740+
HIVE_MERGE_JOIN_SKEW_THRESHOLD("hive.merge.join.skew.threshold", -1L,
1741+
"Maximum number of rows allowed per join key in a single sort-merge join task before a "
1742+
+ "skew event is reported."),
1743+
HIVE_MERGE_JOIN_SKEW_ABORT("hive.merge.join.skew.abort", false,
1744+
"When set to true and the row count is equal to hive.merge.join.skew.threshold, the task will be aborted."),
1745+
HIVE_MERGE_JOIN_SKEW_CHECK_INTERVAL("hive.merge.join.skew.check.interval", 10000L,
1746+
"Number of rows added to a join-key group between consecutive skew checks. "
1747+
+ "A lower value detects skew earlier but adds slightly more overhead. "
1748+
+ "Only effective when hive.merge.join.skew.threshold is set to a positive value."),
17401749
HIVE_PUSH_RESIDUAL_INNER("hive.join.inner.residual", false,
17411750
"Whether to push non-equi filter predicates within inner joins. This can improve efficiency in "
17421751
+ "the evaluation of certain joins, since we will not be emitting rows which are thrown away by "

ql/src/java/org/apache/hadoop/hive/ql/exec/CommonMergeJoinOperator.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import java.util.TreeSet;
2828

2929
import org.apache.hadoop.hive.ql.exec.tez.ReduceRecordSource;
30+
import org.apache.hadoop.hive.ql.exec.tez.monitoring.SkewedJoinMonitor;
31+
import org.apache.hadoop.hive.ql.exec.tez.monitoring.SkewedMergeJoinMonitor;
32+
import org.apache.hadoop.hive.ql.plan.ExprNodeDesc;
3033
import org.apache.hadoop.hive.ql.util.NullOrdering;
3134
import org.apache.hadoop.hive.serde.serdeConstants;
3235
import org.slf4j.Logger;
@@ -40,7 +43,6 @@
4043
import org.apache.hadoop.hive.ql.exec.tez.TezContext;
4144
import org.apache.hadoop.hive.ql.metadata.HiveException;
4245
import org.apache.hadoop.hive.ql.plan.CommonMergeJoinDesc;
43-
import org.apache.hadoop.hive.ql.plan.ExprNodeDesc;
4446
import org.apache.hadoop.hive.ql.plan.JoinCondDesc;
4547
import org.apache.hadoop.hive.ql.plan.JoinDesc;
4648
import org.apache.hadoop.hive.ql.plan.OperatorDesc;
@@ -97,6 +99,10 @@ public class CommonMergeJoinOperator extends AbstractMapJoinOperator<CommonMerge
9799
transient NullOrdering nullOrdering;
98100
transient private boolean shortcutUnmatchedRows;
99101

102+
transient SkewedJoinMonitor skewedMergeJoinMonitor;
103+
transient String[] joinSkewKeyColumns;
104+
transient String[] joinSkewTableAliases;
105+
100106
/** Kryo ctor. */
101107
protected CommonMergeJoinOperator() {
102108
super();
@@ -139,6 +145,13 @@ public void initializeOp(Configuration hconf) throws HiveException {
139145
int oldVar = HiveConf.getIntVar(hconf, HiveConf.ConfVars.HIVE_MAPJOIN_BUCKET_CACHE_SIZE);
140146
shortcutUnmatchedRows = HiveConf.getBoolVar(hconf, HiveConf.ConfVars.HIVE_JOIN_SHORTCUT_UNMATCHED_ROWS);
141147

148+
skewedMergeJoinMonitor = SkewedMergeJoinMonitor.createSkewedJoinMonitor(
149+
HiveConf.getLongVar(hconf, HiveConf.ConfVars.HIVE_MERGE_JOIN_SKEW_THRESHOLD),
150+
HiveConf.getBoolVar(hconf, HiveConf.ConfVars.HIVE_MERGE_JOIN_SKEW_ABORT),
151+
HiveConf.getLongVar(hconf, HiveConf.ConfVars.HIVE_MERGE_JOIN_SKEW_CHECK_INTERVAL), maxAlias);
152+
153+
initSkewJoinNames(maxAlias);
154+
142155
if (oldVar != 100) {
143156
bucketSize = oldVar;
144157
} else {
@@ -222,6 +235,23 @@ private Set<Integer> getFetchInputAtCloseList() {
222235
return retval;
223236
}
224237

238+
private void initSkewJoinNames(int maxAlias) {
239+
joinSkewKeyColumns = new String[maxAlias];
240+
joinSkewTableAliases = new String[maxAlias];
241+
242+
String[] descKeyNames = conf.getSkewJoinKeyNames();
243+
String[] descTableAliases = conf.getSkewJoinTableAliases();
244+
245+
for (int pos = 0; pos < maxAlias; pos++) {
246+
joinSkewKeyColumns[pos] =
247+
(descKeyNames != null && pos < descKeyNames.length && descKeyNames[pos] != null) ? descKeyNames[pos]
248+
: "unknown";
249+
joinSkewTableAliases[pos] =
250+
(descTableAliases != null && pos < descTableAliases.length && descTableAliases[pos] != null)
251+
? descTableAliases[pos] : "unknown";
252+
}
253+
}
254+
225255
@Override
226256
public void endGroup() throws HiveException {
227257
// we do not want the end group to cause a checkAndGenObject
@@ -322,6 +352,9 @@ public void process(Object row, int tag) throws HiveException {
322352

323353
assert !nextKeyGroup;
324354
candidateStorage[tag].addRow(value);
355+
356+
skewedMergeJoinMonitor.checkMergeJoinSkew(alias, candidateStorage[tag].rowCount(),
357+
joinSkewKeyColumns[alias], joinSkewTableAliases[alias]);
325358
}
326359

327360
private void emitUnmatchedRows(int tag, boolean force) throws HiveException {
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.hive.ql.exec.tez.monitoring;
20+
21+
public class NoopSkewedMergeJoinMonitor implements SkewedJoinMonitor {
22+
23+
@Override
24+
public void checkMergeJoinSkew(byte alias, long rowCount, String joinKeyColumns, String tableAlias) {
25+
// No-op
26+
}
27+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.hive.ql.exec.tez.monitoring;
20+
21+
import org.apache.hadoop.hive.ql.metadata.HiveException;
22+
23+
public interface SkewedJoinMonitor {
24+
25+
/**
26+
* Checks whether a skewed data event is detected during a merge join for a given alias and its
27+
* row count. If skew is detected, a warning is logged or an exception is thrown, depending on
28+
* the configured action.
29+
*
30+
* @param alias the byte identifier of the join alias
31+
* @param rowCount the number of rows accumulated for the current join key on this alias
32+
* @param joinKeyColumns the join key column name(s) resolved via RowSchema for the given alias
33+
* (schema info, not data values — e.g. "customer_id")
34+
* @param tableAlias the table/subquery alias for the given alias (big or small table)
35+
* @throws HiveException if skew is detected and abort mode is enabled
36+
*/
37+
void checkMergeJoinSkew(byte alias, long rowCount, String joinKeyColumns, String tableAlias) throws HiveException;
38+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.hive.ql.exec.tez.monitoring;
20+
21+
import com.google.common.annotations.VisibleForTesting;
22+
import org.apache.hadoop.hive.ql.metadata.HiveException;
23+
import org.slf4j.Logger;
24+
import org.slf4j.LoggerFactory;
25+
26+
public class SkewedMergeJoinMonitor implements SkewedJoinMonitor {
27+
28+
private final long mergeJoinSkewThreshold;
29+
private final boolean mergeJoinSkewAbort;
30+
private final long mergeJoinSkewCheckInterval;
31+
private final boolean[] skewedKeyFlagged;
32+
33+
private static final Logger LOG = LoggerFactory.getLogger(SkewedMergeJoinMonitor.class.getName());
34+
35+
public SkewedMergeJoinMonitor(long mergeJoinSkewThreshold, boolean mergeJoinSkewAbort,
36+
long mergeJoinSkewCheckInterval, int maxAlias) {
37+
this.mergeJoinSkewThreshold = mergeJoinSkewThreshold;
38+
this.mergeJoinSkewAbort = mergeJoinSkewAbort;
39+
this.mergeJoinSkewCheckInterval = mergeJoinSkewCheckInterval > 0 ? mergeJoinSkewCheckInterval : 1;
40+
skewedKeyFlagged = new boolean[maxAlias];
41+
}
42+
43+
@VisibleForTesting
44+
public boolean isActive() {
45+
return mergeJoinSkewThreshold > 0;
46+
}
47+
48+
@VisibleForTesting
49+
public boolean shouldBeFlagged(byte alias, long rowCount) {
50+
return rowCount >= mergeJoinSkewThreshold && !skewedKeyFlagged[alias];
51+
}
52+
53+
@VisibleForTesting
54+
public boolean isFlagged(int alias) {
55+
return skewedKeyFlagged[alias];
56+
}
57+
58+
@VisibleForTesting
59+
public boolean isDueForCheck(long rowCount) {
60+
return (rowCount % mergeJoinSkewCheckInterval == 0) || (rowCount >= mergeJoinSkewThreshold);
61+
}
62+
63+
@Override
64+
public void checkMergeJoinSkew(byte alias, long rowCount, String joinKeyColumns, String tableAlias)
65+
throws HiveException {
66+
if (!isActive()) {
67+
return;
68+
}
69+
if (skewedKeyFlagged[alias]) {
70+
return;
71+
}
72+
if (!isDueForCheck(rowCount)) {
73+
return;
74+
}
75+
if (!shouldBeFlagged(alias, rowCount)) {
76+
return;
77+
}
78+
79+
skewedKeyFlagged[alias] = true;
80+
81+
String msg = String.format(
82+
"Data skew detected in merge join: %d rows accumulated for join column(s) [%s]"
83+
+ " in table alias [%s]. Consider reviewing data distribution.",
84+
rowCount, joinKeyColumns, tableAlias);
85+
86+
if (mergeJoinSkewAbort) {
87+
throw new HiveException(msg);
88+
} else {
89+
LOG.warn(msg);
90+
}
91+
}
92+
93+
public static SkewedJoinMonitor createSkewedJoinMonitor(long mergeJoinSkewThreshold, boolean mergeJoinSkewAbort,
94+
long mergeJoinSkewCheckInterval, int maxAlias) {
95+
if (mergeJoinSkewThreshold > 0) {
96+
return new SkewedMergeJoinMonitor(mergeJoinSkewThreshold, mergeJoinSkewAbort, mergeJoinSkewCheckInterval,
97+
maxAlias);
98+
} else {
99+
return new NoopSkewedMergeJoinMonitor();
100+
}
101+
}
102+
}

ql/src/java/org/apache/hadoop/hive/ql/optimizer/ConvertJoinMapJoin.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import org.apache.hadoop.hive.ql.lib.SemanticNodeProcessor;
5353
import org.apache.hadoop.hive.ql.lib.NodeProcessorCtx;
5454
import org.apache.hadoop.hive.ql.metadata.Table;
55+
import org.apache.hadoop.hive.ql.optimizer.metainfo.query.JoinOperationMetadataResolver;
5556
import org.apache.hadoop.hive.ql.optimizer.physical.LlapClusterStateForCompile;
5657
import org.apache.hadoop.hive.ql.parse.GenTezUtils;
5758
import org.apache.hadoop.hive.ql.parse.OptimizeTezProcContext;
@@ -626,6 +627,30 @@ private void convertJoinSMBJoin(JoinOperator joinOp, OptimizeTezProcContext cont
626627
}
627628
}
628629
mergeJoinOp.cloneOriginalParentsList(mergeJoinOp.getParentOperators());
630+
631+
// Resolve original table names and key column names from the compile-time
632+
// operator tree only when skew monitoring is actually enabled
633+
// (hive.merge.join.skew.threshold > 0). Tree traversal is skipped
634+
// entirely when the feature is off so there is no overhead for the
635+
// common case.
636+
if (HiveConf.getLongVar(context.conf, HiveConf.ConfVars.HIVE_MERGE_JOIN_SKEW_THRESHOLD) > 0) {
637+
JoinOperationMetadataResolver resolver = new JoinOperationMetadataResolver();
638+
populateSkewJoinNames(resolver, joinOp, mergeJoinOp);
639+
}
640+
}
641+
642+
/**
643+
* The results are stored as non-transient fields in
644+
* {@link CommonMergeJoinDesc} so they survive plan serialization to the Tez task
645+
* and can be read by the skew-join monitor at runtime.
646+
*
647+
*/
648+
private void populateSkewJoinNames(JoinOperationMetadataResolver resolver, JoinOperator joinOp,
649+
CommonMergeJoinOperator mergeJoinOp) {
650+
resolver.resolveJoinMetadata(joinOp);
651+
652+
mergeJoinOp.getConf().setSkewJoinKeyNames(resolver.getKeyNames());
653+
mergeJoinOp.getConf().setSkewJoinTableAliases(resolver.getTableAliases());
629654
}
630655

631656
private void setAllChildrenTraits(Operator<? extends OperatorDesc> currentOp, OpTraits opTraits) {

0 commit comments

Comments
 (0)