Look into parameter value when estimating plan cost
Summary: Test cost estimator considers parameter values Reviewers: florijan, mislav.bradac Reviewed By: florijan Subscribers: pullbot Differential Revision: https://phabricator.memgraph.io/D798
This commit is contained in:
parent
960c5e6092
commit
b31478dfd8
@ -117,7 +117,7 @@ class Interpreter {
|
||||
ast_storage, ctx.symbol_table_, vertex_counts);
|
||||
double min_cost = std::numeric_limits<double>::max();
|
||||
for (auto &plan : plans) {
|
||||
auto cost = EstimatePlanCost(vertex_counts, *plan);
|
||||
auto cost = EstimatePlanCost(vertex_counts, ctx.parameters_, *plan);
|
||||
if (!logical_plan || cost < min_cost) {
|
||||
// We won't be iterating over plans anymore, so it's ok to invalidate
|
||||
// unique_ptrs inside.
|
||||
@ -130,7 +130,7 @@ class Interpreter {
|
||||
logical_plan = plan::MakeLogicalPlan<plan::RuleBasedPlanner>(
|
||||
ast_storage, ctx.symbol_table_, vertex_counts);
|
||||
query_plan_cost_estimation =
|
||||
EstimatePlanCost(vertex_counts, *logical_plan);
|
||||
EstimatePlanCost(vertex_counts, ctx.parameters_, *logical_plan);
|
||||
}
|
||||
|
||||
// generate frame based on symbol table max_position
|
||||
|
@ -1,4 +1,5 @@
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/parameters.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
|
||||
@ -64,7 +65,8 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
|
||||
CostEstimator(const TDbAccessor &db_accessor) : db_accessor_(db_accessor) {}
|
||||
CostEstimator(const TDbAccessor &db_accessor, const Parameters ¶meters)
|
||||
: db_accessor_(db_accessor), parameters(parameters) {}
|
||||
|
||||
bool PostVisit(ScanAll &) override {
|
||||
cardinality_ *= db_accessor_.VerticesCount();
|
||||
@ -81,17 +83,10 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
}
|
||||
|
||||
bool PostVisit(ScanAllByLabelPropertyValue &logical_op) override {
|
||||
// this cardinality estimation depends on the property value (expression).
|
||||
// if it's a literal (const) we can evaluate cardinality exactly, otherwise
|
||||
// This cardinality estimation depends on the property value (expression).
|
||||
// If it's a constant, we can evaluate cardinality exactly, otherwise
|
||||
// we estimate
|
||||
std::experimental::optional<PropertyValue> property_value =
|
||||
std::experimental::nullopt;
|
||||
if (auto *literal =
|
||||
dynamic_cast<PrimitiveLiteral *>(logical_op.expression()))
|
||||
if (literal->value_.IsPropertyValue())
|
||||
property_value =
|
||||
std::experimental::optional<PropertyValue>(literal->value_);
|
||||
|
||||
auto property_value = ConstPropertyValue(logical_op.expression());
|
||||
double factor = 1.0;
|
||||
if (property_value)
|
||||
// get the exact influence based on ScanAll(label, property, value)
|
||||
@ -206,27 +201,43 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
|
||||
// accessor used for cardinality estimates in ScanAll and ScanAllByLabel
|
||||
const TDbAccessor &db_accessor_;
|
||||
const Parameters ¶meters;
|
||||
|
||||
void IncrementCost(double param) { cost_ += param * cardinality_; }
|
||||
|
||||
// converts an optional ScanAll range bound into a property value
|
||||
// if the bound is present and is a literal expression convertible to
|
||||
// if the bound is present and is a constant expression convertible to
|
||||
// a property value. otherwise returns nullopt
|
||||
static std::experimental::optional<utils::Bound<PropertyValue>>
|
||||
BoundToPropertyValue(
|
||||
std::experimental::optional<utils::Bound<PropertyValue>> BoundToPropertyValue(
|
||||
std::experimental::optional<ScanAllByLabelPropertyRange::Bound> bound) {
|
||||
if (bound)
|
||||
if (auto *literal = dynamic_cast<PrimitiveLiteral *>(bound->value()))
|
||||
return std::experimental::make_optional(
|
||||
utils::Bound<PropertyValue>(literal->value_, bound->type()));
|
||||
if (bound) {
|
||||
auto property_value = ConstPropertyValue(bound->value());
|
||||
if (property_value)
|
||||
return utils::Bound<PropertyValue>(*property_value, bound->type());
|
||||
}
|
||||
return std::experimental::nullopt;
|
||||
}
|
||||
|
||||
// If the expression is a constant property value, it is returned. Otherwise,
|
||||
// return nullopt.
|
||||
std::experimental::optional<PropertyValue> ConstPropertyValue(
|
||||
const Expression *expression) {
|
||||
if (auto *literal = dynamic_cast<const PrimitiveLiteral *>(expression)) {
|
||||
if (literal->value_.IsPropertyValue()) return literal->value_;
|
||||
} else if (auto *param_lookup =
|
||||
dynamic_cast<const ParameterLookup *>(expression)) {
|
||||
auto value = parameters.AtTokenPosition(param_lookup->token_position_);
|
||||
if (value.IsPropertyValue()) return value;
|
||||
}
|
||||
return std::experimental::nullopt;
|
||||
}
|
||||
};
|
||||
|
||||
/** Returns the estimated cost of the given plan. */
|
||||
template <class TDbAccessor>
|
||||
double EstimatePlanCost(const TDbAccessor &db, LogicalOperator &plan) {
|
||||
CostEstimator<TDbAccessor> estimator(db);
|
||||
double EstimatePlanCost(const TDbAccessor &db, const Parameters ¶meters,
|
||||
LogicalOperator &plan) {
|
||||
CostEstimator<TDbAccessor> estimator(db, parameters);
|
||||
plan.Accept(estimator);
|
||||
return estimator.cost();
|
||||
}
|
||||
|
@ -95,6 +95,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
|
||||
std::tie(label, prop) =
|
||||
CreateIndexedVertices(index_count, vertex_count, dbms);
|
||||
auto dba = dbms.active();
|
||||
Parameters parameters;
|
||||
while (state.KeepRunning()) {
|
||||
state.PauseTiming();
|
||||
query::AstTreeStorage storage;
|
||||
@ -108,7 +109,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
|
||||
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(
|
||||
storage, symbol_table, *dba);
|
||||
for (auto &plan : plans) {
|
||||
query::plan::EstimatePlanCost(*dba, *plan);
|
||||
query::plan::EstimatePlanCost(*dba, parameters, *plan);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -124,6 +125,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(
|
||||
CreateIndexedVertices(index_count, vertex_count, dbms);
|
||||
auto dba = dbms.active();
|
||||
auto vertex_counts = query::plan::MakeVertexCountCache(*dba);
|
||||
Parameters parameters;
|
||||
while (state.KeepRunning()) {
|
||||
state.PauseTiming();
|
||||
query::AstTreeStorage storage;
|
||||
@ -137,7 +139,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(
|
||||
query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(
|
||||
storage, symbol_table, vertex_counts);
|
||||
for (auto &plan : plans) {
|
||||
query::plan::EstimatePlanCost(vertex_counts, *plan);
|
||||
query::plan::EstimatePlanCost(vertex_counts, parameters, *plan);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -613,8 +613,10 @@ auto MakeLogicalPlans(query::AstTreeStorage &ast,
|
||||
plans_with_cost;
|
||||
auto plans = query::plan::MakeLogicalPlan<query::plan::VariableStartPlanner>(
|
||||
ast, symbol_table, dba);
|
||||
Parameters parameters;
|
||||
for (auto &plan : plans) {
|
||||
query::plan::CostEstimator<InteractiveDbAccessor> estimator(dba);
|
||||
query::plan::CostEstimator<InteractiveDbAccessor> estimator(dba,
|
||||
parameters);
|
||||
plan->Accept(estimator);
|
||||
plans_with_cost.emplace_back(std::move(plan), estimator.cost());
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class QueryCostEstimator : public ::testing::Test {
|
||||
|
||||
AstTreeStorage storage_;
|
||||
SymbolTable symbol_table_;
|
||||
Parameters parameters_;
|
||||
int symbol_count = 0;
|
||||
|
||||
void SetUp() {
|
||||
@ -61,7 +62,7 @@ class QueryCostEstimator : public ::testing::Test {
|
||||
}
|
||||
|
||||
auto Cost() {
|
||||
CostEstimator<GraphDbAccessor> cost_estimator(*dba);
|
||||
CostEstimator<GraphDbAccessor> cost_estimator(*dba, parameters_);
|
||||
last_op_->Accept(cost_estimator);
|
||||
return cost_estimator.cost();
|
||||
}
|
||||
@ -76,9 +77,15 @@ class QueryCostEstimator : public ::testing::Test {
|
||||
return storage_.Create<PrimitiveLiteral>(value);
|
||||
}
|
||||
|
||||
auto InclusiveBound(int bound) {
|
||||
Expression *Parameter(const TypedValue &value) {
|
||||
int token_position = parameters_.size();
|
||||
parameters_.Add(token_position, value);
|
||||
return storage_.Create<ParameterLookup>(token_position);
|
||||
}
|
||||
|
||||
auto InclusiveBound(Expression *expression) {
|
||||
return std::experimental::make_optional(
|
||||
utils::MakeBoundInclusive(Literal(bound)));
|
||||
utils::MakeBoundInclusive(expression));
|
||||
};
|
||||
|
||||
const std::experimental::nullopt_t nullopt = std::experimental::nullopt;
|
||||
@ -101,48 +108,58 @@ TEST_F(QueryCostEstimator, ScanAllByLabelCardinality) {
|
||||
EXPECT_COST(30 * CostParam::kScanAllByLabel);
|
||||
}
|
||||
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueLiteral) {
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueConstant) {
|
||||
AddVertices(100, 30, 20);
|
||||
MakeOp<ScanAllByLabelPropertyValue>(last_op_, NextSymbol(), label, property,
|
||||
Literal(12));
|
||||
EXPECT_COST(1 * CostParam::MakeScanAllByLabelPropertyValue);
|
||||
for (auto const_val : {Literal(12), Parameter(12)}) {
|
||||
MakeOp<ScanAllByLabelPropertyValue>(nullptr, NextSymbol(), label, property,
|
||||
const_val);
|
||||
EXPECT_COST(1 * CostParam::MakeScanAllByLabelPropertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueExpr) {
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyValueConstExpr) {
|
||||
AddVertices(100, 30, 20);
|
||||
MakeOp<ScanAllByLabelPropertyValue>(
|
||||
last_op_, NextSymbol(), label, property,
|
||||
// once we make expression const-folding this test case will fail
|
||||
storage_.Create<UnaryPlusOperator>(Literal(12)));
|
||||
EXPECT_COST(20 * CardParam::kFilter *
|
||||
CostParam::MakeScanAllByLabelPropertyValue);
|
||||
for (auto const_val : {Literal(12), Parameter(12)}) {
|
||||
MakeOp<ScanAllByLabelPropertyValue>(
|
||||
nullptr, NextSymbol(), label, property,
|
||||
// once we make expression const-folding this test case will fail
|
||||
storage_.Create<UnaryPlusOperator>(const_val));
|
||||
EXPECT_COST(20 * CardParam::kFilter *
|
||||
CostParam::MakeScanAllByLabelPropertyValue);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeUpper) {
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeUpperConstant) {
|
||||
AddVertices(100, 30, 20);
|
||||
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
|
||||
nullopt, InclusiveBound(12));
|
||||
// cardinality estimation is exact for very small indexes
|
||||
EXPECT_COST(13 * CostParam::MakeScanAllByLabelPropertyRange);
|
||||
for (auto const_val : {Literal(12), Parameter(12)}) {
|
||||
MakeOp<ScanAllByLabelPropertyRange>(nullptr, NextSymbol(), label, property,
|
||||
nullopt, InclusiveBound(const_val));
|
||||
// cardinality estimation is exact for very small indexes
|
||||
EXPECT_COST(13 * CostParam::MakeScanAllByLabelPropertyRange);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeLower) {
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeLowerConstant) {
|
||||
AddVertices(100, 30, 20);
|
||||
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
|
||||
InclusiveBound(17), nullopt);
|
||||
// cardinality estimation is exact for very small indexes
|
||||
EXPECT_COST(3 * CostParam::MakeScanAllByLabelPropertyRange);
|
||||
for (auto const_val : {Literal(17), Parameter(17)}) {
|
||||
MakeOp<ScanAllByLabelPropertyRange>(nullptr, NextSymbol(), label, property,
|
||||
InclusiveBound(const_val), nullopt);
|
||||
// cardinality estimation is exact for very small indexes
|
||||
EXPECT_COST(3 * CostParam::MakeScanAllByLabelPropertyRange);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeNonLiteral) {
|
||||
TEST_F(QueryCostEstimator, ScanAllByLabelPropertyRangeConstExpr) {
|
||||
AddVertices(100, 30, 20);
|
||||
auto bound = std::experimental::make_optional(
|
||||
utils::MakeBoundInclusive(static_cast<Expression *>(
|
||||
storage_.Create<UnaryPlusOperator>(Literal(12)))));
|
||||
MakeOp<ScanAllByLabelPropertyRange>(last_op_, NextSymbol(), label, property,
|
||||
bound, nullopt);
|
||||
EXPECT_COST(20 * CardParam::kFilter *
|
||||
CostParam::MakeScanAllByLabelPropertyRange);
|
||||
for (auto const_val : {Literal(12), Parameter(12)}) {
|
||||
auto bound = std::experimental::make_optional(
|
||||
utils::MakeBoundInclusive(static_cast<Expression *>(
|
||||
storage_.Create<UnaryPlusOperator>(const_val))));
|
||||
MakeOp<ScanAllByLabelPropertyRange>(nullptr, NextSymbol(), label, property,
|
||||
bound, nullopt);
|
||||
EXPECT_COST(20 * CardParam::kFilter *
|
||||
CostParam::MakeScanAllByLabelPropertyRange);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(QueryCostEstimator, Expand) {
|
||||
|
Loading…
Reference in New Issue
Block a user