Introduce scopes in cost estimator
This commit is contained in:
@@ -91,11 +91,11 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
using HierarchicalLogicalOperatorVisitor::PostVisit;
|
||||
using HierarchicalLogicalOperatorVisitor::PreVisit;
|
||||
|
||||
CostEstimator(TDbAccessor *db_accessor, const Parameters ¶meters)
|
||||
: db_accessor_(db_accessor), parameters(parameters), scope_(Scope()) {}
|
||||
CostEstimator(TDbAccessor *db_accessor, const Parameters ¶meters, const SymbolTable &table)
|
||||
: db_accessor_(db_accessor), parameters(parameters), table_(table), scopes_{Scope()} {}
|
||||
|
||||
CostEstimator(TDbAccessor *db_accessor, const Parameters ¶meters, Scope scope)
|
||||
: db_accessor_(db_accessor), parameters(parameters), scope_(scope) {}
|
||||
CostEstimator(TDbAccessor *db_accessor, const Parameters ¶meters, const SymbolTable &table, Scope scope)
|
||||
: db_accessor_(db_accessor), parameters(parameters), table_(table), scopes_{scope} {}
|
||||
|
||||
bool PostVisit(ScanAll &) override {
|
||||
cardinality_ *= db_accessor_->VerticesCount();
|
||||
@@ -186,9 +186,13 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
// TODO: Cost estimate ScanAllById?
|
||||
|
||||
bool PostVisit(Expand &expand) override {
|
||||
const auto &scope = scopes_.back();
|
||||
auto card_param = CardParam::kExpand;
|
||||
if (HasStatsFor(expand.input_symbol_)) {
|
||||
card_param = scope_.symbol_stats[expand.input_symbol_.name()].degree;
|
||||
|
||||
auto stats = GetStatsFor(expand.input_symbol_);
|
||||
|
||||
if (stats.has_value()) {
|
||||
card_param = stats.value().degree;
|
||||
}
|
||||
|
||||
cardinality_ *= card_param;
|
||||
@@ -269,6 +273,21 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool PostVisit(Produce &op) override {
|
||||
auto scope = Scope();
|
||||
|
||||
for (const auto &symbol : op.ModifiedSymbols(table_)) {
|
||||
auto stats = GetStatsFor(symbol);
|
||||
if (stats.has_value()) {
|
||||
scope.symbol_stats[symbol.name()] =
|
||||
SymbolStatistics{.cardinality = stats.value().cardinality, .degree = stats.value().degree};
|
||||
}
|
||||
}
|
||||
|
||||
scopes_.push_back(scope);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PreVisit(Apply &op) override {
|
||||
double input_cost = EstimateCostOnBranch(&op.input_);
|
||||
double subquery_cost = EstimateCostOnBranch(&op.subquery_);
|
||||
@@ -300,12 +319,13 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
// accessor used for cardinality estimates in ScanAll and ScanAllByLabel
|
||||
TDbAccessor *db_accessor_;
|
||||
const Parameters ¶meters;
|
||||
Scope scope_;
|
||||
const SymbolTable &table_;
|
||||
std::vector<Scope> scopes_;
|
||||
|
||||
void IncrementCost(double param) { cost_ += param * cardinality_; }
|
||||
|
||||
double EstimateCostOnBranch(std::shared_ptr<LogicalOperator> *branch) {
|
||||
CostEstimator<TDbAccessor> cost_estimator(db_accessor_, parameters);
|
||||
CostEstimator<TDbAccessor> cost_estimator(db_accessor_, parameters, table_);
|
||||
(*branch)->Accept(cost_estimator);
|
||||
return cost_estimator.cost();
|
||||
}
|
||||
@@ -333,27 +353,39 @@ class CostEstimator : public HierarchicalLogicalOperatorVisitor {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool HasStatsFor(Symbol &symbol) const { return utils::Contains(scope_.symbol_stats, symbol.name()); }
|
||||
bool HasStatsFor(const Symbol &symbol) const { return utils::Contains(scopes_.back().symbol_stats, symbol.name()); }
|
||||
|
||||
void SaveStatsFor(Symbol &symbol, storage::LabelIndexStats index_stats) {
|
||||
scope_.symbol_stats[symbol.name()] = SymbolStatistics{
|
||||
std::optional<SymbolStatistics> GetStatsFor(const Symbol &symbol) {
|
||||
if (!HasStatsFor(symbol)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
auto &scope = scopes_.back();
|
||||
return scope.symbol_stats[symbol.name()];
|
||||
}
|
||||
|
||||
void SaveStatsFor(const Symbol &symbol, storage::LabelIndexStats index_stats) {
|
||||
scopes_.back().symbol_stats[symbol.name()] = SymbolStatistics{
|
||||
.cardinality = index_stats.count,
|
||||
.degree = index_stats.avg_degree,
|
||||
};
|
||||
}
|
||||
|
||||
void SaveStatsFor(Symbol &symbol, storage::LabelPropertyIndexStats index_stats) {
|
||||
scope_.symbol_stats[symbol.name()] = SymbolStatistics{
|
||||
void SaveStatsFor(const Symbol &symbol, storage::LabelPropertyIndexStats index_stats) {
|
||||
scopes_.back().symbol_stats[symbol.name()] = SymbolStatistics{
|
||||
.cardinality = index_stats.count,
|
||||
.degree = index_stats.avg_degree,
|
||||
};
|
||||
}
|
||||
|
||||
void DeleteStatsFor(const Symbol &symbol) { scopes_.back().symbol_stats.erase(symbol.name()); }
|
||||
};
|
||||
|
||||
/** Returns the estimated cost of the given plan. */
|
||||
template <class TDbAccessor>
|
||||
double EstimatePlanCost(TDbAccessor *db, const Parameters ¶meters, LogicalOperator &plan) {
|
||||
CostEstimator<TDbAccessor> estimator(db, parameters);
|
||||
double EstimatePlanCost(TDbAccessor *db, const Parameters ¶meters, LogicalOperator &plan,
|
||||
const SymbolTable &table) {
|
||||
CostEstimator<TDbAccessor> estimator(db, parameters, table);
|
||||
plan.Accept(estimator);
|
||||
return estimator.cost();
|
||||
}
|
||||
|
||||
@@ -47,8 +47,9 @@ class PostProcessor final {
|
||||
}
|
||||
|
||||
template <class TVertexCounts>
|
||||
double EstimatePlanCost(const std::unique_ptr<LogicalOperator> &plan, TVertexCounts *vertex_counts) {
|
||||
return query::plan::EstimatePlanCost(vertex_counts, parameters_, *plan);
|
||||
double EstimatePlanCost(const std::unique_ptr<LogicalOperator> &plan, TVertexCounts *vertex_counts,
|
||||
SymbolTable &table) {
|
||||
return query::plan::EstimatePlanCost(vertex_counts, parameters_, *plan, table);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -97,7 +98,7 @@ auto MakeLogicalPlan(TPlanningContext *context, TPlanPostProcess *post_process,
|
||||
// Plans are generated lazily and the current plan will disappear, so
|
||||
// it's ok to move it.
|
||||
auto rewritten_plan = post_process->Rewrite(std::move(plan), context);
|
||||
double cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts);
|
||||
double cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table);
|
||||
if (!curr_plan || cost < total_cost) {
|
||||
curr_plan.emplace(std::move(rewritten_plan));
|
||||
total_cost = cost;
|
||||
@@ -106,7 +107,7 @@ auto MakeLogicalPlan(TPlanningContext *context, TPlanPostProcess *post_process,
|
||||
} else {
|
||||
auto plan = MakeLogicalPlanForSingleQuery<RuleBasedPlanner>(query_parts, context);
|
||||
auto rewritten_plan = post_process->Rewrite(std::move(plan), context);
|
||||
total_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts);
|
||||
total_cost = post_process->EstimatePlanCost(rewritten_plan, &vertex_counts, *context->symbol_table);
|
||||
curr_plan.emplace(std::move(rewritten_plan));
|
||||
}
|
||||
|
||||
|
||||
@@ -505,7 +505,7 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
// FilterInfo with PropertyFilter.
|
||||
FilterInfo filter;
|
||||
int64_t vertex_count;
|
||||
std::optional<storage::IndexStats> index_stats;
|
||||
std::optional<storage::LabelPropertyIndexStats> index_stats;
|
||||
};
|
||||
|
||||
bool DefaultPreVisit() override { throw utils::NotYetImplemented("optimizing index lookup"); }
|
||||
@@ -572,8 +572,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
* @param vertex_count: New index's number of vertices.
|
||||
* @return -1 if the new index is better, 0 if they are equal and 1 if the existing one is better.
|
||||
*/
|
||||
auto compare_indices = [](std::optional<LabelPropertyIndex> &found, std::optional<storage::IndexStats> &new_stats,
|
||||
int vertex_count) {
|
||||
auto compare_indices = [](std::optional<LabelPropertyIndex> &found,
|
||||
std::optional<storage::LabelPropertyIndexStats> &new_stats, int vertex_count) {
|
||||
if (!new_stats.has_value()) {
|
||||
return 0;
|
||||
}
|
||||
@@ -610,7 +610,8 @@ class IndexLookupRewriter final : public HierarchicalLogicalOperatorVisitor {
|
||||
};
|
||||
|
||||
int64_t vertex_count = db_->VerticesCount(GetLabel(label), GetProperty(property));
|
||||
std::optional<storage::IndexStats> new_stats = db_->GetIndexStats(GetLabel(label), GetProperty(property));
|
||||
std::optional<storage::LabelPropertyIndexStats> new_stats =
|
||||
db_->GetIndexStats(GetLabel(label), GetProperty(property));
|
||||
|
||||
// Conditions, from more to less important:
|
||||
// the index with 10x less vertices is better.
|
||||
|
||||
@@ -131,7 +131,7 @@ static void BM_PlanAndEstimateIndexedMatching(benchmark::State &state) {
|
||||
auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>(
|
||||
query_parts, &ctx);
|
||||
for (auto plan : plans) {
|
||||
memgraph::query::plan::EstimatePlanCost(&dba, parameters, *plan);
|
||||
memgraph::query::plan::EstimatePlanCost(&dba, parameters, *plan, symbol_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ static void BM_PlanAndEstimateIndexedMatchingWithCachedCounts(benchmark::State &
|
||||
auto plans = memgraph::query::plan::MakeLogicalPlanForSingleQuery<memgraph::query::plan::VariableStartPlanner>(
|
||||
query_parts, &ctx);
|
||||
for (auto plan : plans) {
|
||||
memgraph::query::plan::EstimatePlanCost(&vertex_counts, parameters, *plan);
|
||||
memgraph::query::plan::EstimatePlanCost(&vertex_counts, parameters, *plan, symbol_table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ auto MakeLogicalPlans(memgraph::query::CypherQuery *query, memgraph::query::AstS
|
||||
memgraph::query::AstStorage ast_copy;
|
||||
auto unoptimized_plan = plan->Clone(&ast_copy);
|
||||
auto rewritten_plan = post_process.Rewrite(std::move(plan), &ctx);
|
||||
double cost = post_process.EstimatePlanCost(rewritten_plan, dba);
|
||||
double cost = post_process.EstimatePlanCost(rewritten_plan, dba, symbol_table);
|
||||
interactive_plans.push_back(
|
||||
InteractivePlan{std::move(unoptimized_plan), std::move(ast_copy), std::move(rewritten_plan), cost});
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@ class QueryCostEstimator : public ::testing::Test {
|
||||
}
|
||||
|
||||
auto Cost() {
|
||||
CostEstimator<memgraph::query::DbAccessor> cost_estimator(&*dba, parameters_);
|
||||
CostEstimator<memgraph::query::DbAccessor> cost_estimator(&*dba, parameters_, symbol_table_);
|
||||
last_op_->Accept(cost_estimator);
|
||||
return cost_estimator.cost();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user