Optimize JoinCondition matching (#9200)

* Optimize JoinCondition matching

The LookupJoinMatcher needs to check if a condition is always true or false
multiple times. This can be pre-computed to speed up the match checking

This change reduces the time it takes to perform a for joining on a long key
from ~ 36 ms/op to 23 ms/ op

* Rename variables

* fix typo
This commit is contained in:
Suneet Saldanha 2020-01-21 09:11:50 -08:00 committed by Fangjin Yang
parent f511af1306
commit a2939bbd1a
2 changed files with 21 additions and 9 deletions

View File

@ -28,6 +28,7 @@ import org.apache.druid.math.expr.Parser;
import org.apache.druid.query.expression.ExprUtils; import org.apache.druid.query.expression.ExprUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional; import java.util.Optional;
@ -50,6 +51,9 @@ public class JoinConditionAnalysis
private final String originalExpression; private final String originalExpression;
private final List<Equality> equiConditions; private final List<Equality> equiConditions;
private final List<Expr> nonEquiConditions; private final List<Expr> nonEquiConditions;
private final boolean isAlwaysFalse;
private final boolean isAlwaysTrue;
private final boolean canHashJoin;
private JoinConditionAnalysis( private JoinConditionAnalysis(
final String originalExpression, final String originalExpression,
@ -58,8 +62,15 @@ public class JoinConditionAnalysis
) )
{ {
this.originalExpression = Preconditions.checkNotNull(originalExpression, "originalExpression"); this.originalExpression = Preconditions.checkNotNull(originalExpression, "originalExpression");
this.equiConditions = equiConditions; this.equiConditions = Collections.unmodifiableList(equiConditions);
this.nonEquiConditions = nonEquiConditions; this.nonEquiConditions = Collections.unmodifiableList(nonEquiConditions);
// if any nonEquiCondition is an expression and it evaluates to false
isAlwaysFalse = nonEquiConditions.stream()
.anyMatch(expr -> expr.isLiteral() && !expr.eval(ExprUtils.nilBindings()).asBoolean());
// if there are no equiConditions and all nonEquiConditions are literals and the evaluate to true
isAlwaysTrue = equiConditions.isEmpty() && nonEquiConditions.stream()
.allMatch(expr -> expr.isLiteral() && expr.eval(ExprUtils.nilBindings()).asBoolean());
canHashJoin = nonEquiConditions.stream().allMatch(Expr::isLiteral);
} }
/** /**
@ -141,8 +152,7 @@ public class JoinConditionAnalysis
*/ */
public boolean isAlwaysFalse() public boolean isAlwaysFalse()
{ {
return nonEquiConditions.stream() return isAlwaysFalse;
.anyMatch(expr -> expr.isLiteral() && !expr.eval(ExprUtils.nilBindings()).asBoolean());
} }
/** /**
@ -150,9 +160,7 @@ public class JoinConditionAnalysis
*/ */
public boolean isAlwaysTrue() public boolean isAlwaysTrue()
{ {
return equiConditions.isEmpty() && return isAlwaysTrue;
nonEquiConditions.stream()
.allMatch(expr -> expr.isLiteral() && expr.eval(ExprUtils.nilBindings()).asBoolean());
} }
/** /**
@ -160,7 +168,7 @@ public class JoinConditionAnalysis
*/ */
public boolean canHashJoin() public boolean canHashJoin()
{ {
return nonEquiConditions.stream().allMatch(Expr::isLiteral); return canHashJoin;
} }
@Override @Override

View File

@ -267,7 +267,11 @@ public class JoinConditionAnalysisTest
{ {
EqualsVerifier.forClass(JoinConditionAnalysis.class) EqualsVerifier.forClass(JoinConditionAnalysis.class)
.usingGetClass() .usingGetClass()
.withIgnoredFields("equiConditions", "nonEquiConditions") .withIgnoredFields(
// These fields are tightly coupled with originalExpression
"equiConditions", "nonEquiConditions",
// These fields are calculated from nonEquiConditions
"isAlwaysTrue", "isAlwaysFalse", "canHashJoin")
.verify(); .verify();
} }