Skip to content

Commit

Permalink
fix(interactive): Limit Maximum Hops in Path Expand (#4451)
Browse files Browse the repository at this point in the history
<!--
Thanks for your contribution! please review
https://github.com/alibaba/GraphScope/blob/main/CONTRIBUTING.md before
opening an issue.
-->

## What do these changes do?

1. Limit the maximum hops in path expansion to prevent compilation
errors and excessive consumption of computational resources during
execution.
2. Handle invalid hops, i.e. `Match (a)-[e:4..3]-(b)`, which will throw
exceptions containing `exceeds the maximum allowed iterations` at
compiling phase.

<!-- Please give a short brief about these changes. -->

## Related issue number

<!-- Are there any issues opened that will be resolved by merging this
change? -->

Fixes
  • Loading branch information
shirly121 authored Jan 24, 2025
1 parent e54c98d commit 9c11778
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,9 @@ public class YamlConfigs extends Configs {
.put(
"query.execution.timeout.ms",
(Configs configs) -> configs.get("compiler.query_timeout"))
.put(
"query.execution.max.iterations",
(Configs configs) -> configs.get("compiler.query_max_iterations"))
.put("engine.type", (Configs configs) -> configs.get("compute_engine.type"))
.put(
"calcite.default.charset",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.alibaba.graphscope.common.ir.rel.metadata.glogue.GlogueQuery;
import com.alibaba.graphscope.common.ir.rel.metadata.glogue.pattern.*;
import com.alibaba.graphscope.common.ir.rel.metadata.schema.EdgeTypeId;
import com.alibaba.graphscope.common.ir.tools.QueryExecutionValidator;

import org.checkerframework.checker.nullness.qual.Nullable;

Expand Down Expand Up @@ -91,27 +92,26 @@ public double estimate(PatternEdge edge, EdgeTypeId typeId) {
minHop = range.getOffset();
maxHop = range.getOffset() + range.getFetch() - 1;
}
double sum = 0.0d;
for (int hop = minHop; hop <= maxHop; ++hop) {
sum += estimate(edge, typeId, hop);
}
return sum;
}

public double estimate(PatternEdge edge, EdgeTypeId typeId, int hops) {
PatternVertex srcVertex = new SinglePatternVertex(typeId.getSrcLabelId(), 0);
PatternVertex dstVertex = new SinglePatternVertex(typeId.getDstLabelId(), 1);
if (hops == 0) {
return estimate(srcVertex);
}
Pattern edgePattern = new Pattern();
edgePattern.addVertex(srcVertex);
edgePattern.addVertex(dstVertex);
edgePattern.addEdge(
srcVertex, dstVertex, new SinglePatternEdge(srcVertex, dstVertex, typeId, 0));
edgePattern.reordering();
return Math.pow(gq.getRowCount(edgePattern, false), hops)
/ Math.pow(estimate(dstVertex), hops - 1);
double edgeCount = gq.getRowCount(edgePattern, false);
double dstVertexCount = estimate(dstVertex);
double sum = 0.0d;
int baseHop = Math.max(minHop, 1);
double baseHopCount = Math.pow(edgeCount, baseHop) / Math.pow(dstVertexCount, baseHop - 1);
for (int hop = baseHop, iters = 0;
hop <= maxHop && iters < QueryExecutionValidator.SYSTEM_MAX_ITERATIONS;
++hop, ++iters) {
sum += baseHopCount;
baseHopCount *= (edgeCount / dstVertexCount);
}
return sum;
}

private @Nullable PatternVertex getIntersectVertex(Pattern pattern) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.*;
import org.apache.calcite.rel.logical.LogicalValues;
import org.apache.calcite.rel.type.*;
import org.apache.calcite.rex.*;
import org.apache.calcite.sql.SqlAggFunction;
Expand Down Expand Up @@ -181,11 +182,13 @@ public GraphBuilder getV(GetVConfig config) {
config.getAlias(),
getAliasNameWithId(
config.getStartAlias(),
(RelDataType type) ->
(type instanceof GraphSchemaType)
&& ((GraphSchemaType) type).getScanOpt()
== GraphOpt.Source.EDGE
|| type instanceof GraphPathType));
(RelDataType type) -> {
if (input instanceof LogicalValues) return true;
return (type instanceof GraphSchemaType)
&& ((GraphSchemaType) type).getScanOpt()
== GraphOpt.Source.EDGE
|| type instanceof GraphPathType;
}));
replaceTop(getV);
return this;
}
Expand Down Expand Up @@ -225,10 +228,12 @@ public GraphBuilder pathExpand(PathExpandConfig pxdConfig) {
pxdConfig.getAlias(),
getAliasNameWithId(
pxdConfig.getStartAlias(),
(RelDataType type) ->
(type instanceof GraphSchemaType)
&& ((GraphSchemaType) type).getScanOpt()
== GraphOpt.Source.VERTEX));
(RelDataType type) -> {
if (input instanceof LogicalValues) return true;
return (type instanceof GraphSchemaType)
&& ((GraphSchemaType) type).getScanOpt()
== GraphOpt.Source.VERTEX;
}));
replaceTop(pathExpand);
return this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;

import java.io.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.Map;
Expand All @@ -69,6 +71,7 @@ public class GraphPlanner {
private final GraphRelOptimizer optimizer;
private final RexBuilder rexBuilder;
private final LogicalPlanFactory logicalPlanFactory;
private final QueryExecutionValidator validator;

public static final Function<Configs, RexBuilder> rexBuilderFactory =
(Configs configs) -> new GraphRexBuilder(new GraphTypeFactoryImpl(configs));
Expand All @@ -81,6 +84,7 @@ public GraphPlanner(
this.optimizer = optimizer;
this.logicalPlanFactory = logicalPlanFactory;
this.rexBuilder = rexBuilderFactory.apply(graphConfig);
this.validator = new QueryExecutionValidator(graphConfig);
}

public PlannerInstance instance(String query, IrMeta irMeta) {
Expand All @@ -103,8 +107,8 @@ public PlannerInstance instance(
GraphBuilder graphBuilder =
GraphBuilder.create(
graphConfig, optCluster, new GraphOptSchema(optCluster, schema));

LogicalPlan logicalPlan = logicalPlanFactory.create(graphBuilder, irMeta, query);
this.validator.validate(logicalPlan, true);
return new PlannerInstance(query, logicalPlan, graphBuilder, irMeta, queryLogger);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* Copyright 2020 Alibaba Group Holding Limited.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.alibaba.graphscope.common.ir.tools;

import com.alibaba.graphscope.common.config.Config;
import com.alibaba.graphscope.common.config.Configs;
import com.alibaba.graphscope.common.exception.FrontendException;
import com.alibaba.graphscope.common.ir.meta.schema.CommonOptTable;
import com.alibaba.graphscope.common.ir.rel.CommonTableScan;
import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalExpand;
import com.alibaba.graphscope.common.ir.rel.graph.GraphLogicalPathExpand;
import com.alibaba.graphscope.common.ir.rel.graph.GraphPhysicalExpand;
import com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalMultiMatch;
import com.alibaba.graphscope.common.ir.rel.graph.match.GraphLogicalSingleMatch;
import com.alibaba.graphscope.proto.frontend.Code;
import com.google.common.collect.Lists;

import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rex.RexLiteral;

import java.util.List;

public class QueryExecutionValidator {
public static final int SYSTEM_MAX_ITERATIONS = 15;

private static final Config<Integer> CONFIG_MAX_ITERATIONS =
Config.intConfig("query.execution.max.iterations", 15);

private final int maxIterations;

public QueryExecutionValidator(Configs configs) {
int maxIterations = CONFIG_MAX_ITERATIONS.get(configs);
if (maxIterations > SYSTEM_MAX_ITERATIONS) {
throw new FrontendException(
Code.LOGICAL_PLAN_BUILD_FAILED,
"max iterations "
+ maxIterations
+ " exceeds the system limit "
+ SYSTEM_MAX_ITERATIONS);
}
this.maxIterations = maxIterations;
}

public boolean validate(LogicalPlan plan, boolean throwsOnFail) {
if (plan.getRegularQuery() == null || plan.isReturnEmpty()) return true;
int hops = 0;
List<RelNode> queue = Lists.newArrayList(plan.getRegularQuery());
while (!queue.isEmpty()) {
RelNode top = queue.remove(0);
if (top instanceof GraphLogicalExpand) {
++hops;
} else if (top instanceof GraphPhysicalExpand) {
++hops;
} else if (top instanceof GraphLogicalPathExpand) {
GraphLogicalPathExpand pxd = (GraphLogicalPathExpand) top;
// null means no limit
if (pxd.getFetch() == null) {
if (throwsOnFail) {
throw new FrontendException(
Code.LOGICAL_PLAN_BUILD_FAILED,
"path expand with no upper bound exceeds the maximum allowed"
+ " iterations "
+ maxIterations);
}
return false;
}
int lower =
(pxd.getOffset() == null)
? 0
: ((Number) ((RexLiteral) pxd.getOffset()).getValue()).intValue();
hops +=
(lower
+ ((Number) ((RexLiteral) pxd.getFetch()).getValue()).intValue()
- 1);
} else if (top instanceof GraphLogicalSingleMatch) {
validate(
new LogicalPlan(((GraphLogicalSingleMatch) top).getSentence()),
throwsOnFail);
} else if (top instanceof GraphLogicalMultiMatch) {
for (RelNode sentence : ((GraphLogicalMultiMatch) top).getSentences()) {
validate(new LogicalPlan(sentence), throwsOnFail);
}
} else if (top instanceof CommonTableScan) {
CommonOptTable optTable = (CommonOptTable) top.getTable();
validate(new LogicalPlan(optTable.getCommon()), throwsOnFail);
}
queue.addAll(top.getInputs());
}
if (hops <= maxIterations) return true;
if (throwsOnFail) {
throw new FrontendException(
Code.LOGICAL_PLAN_BUILD_FAILED,
"query hops "
+ hops
+ " exceeds the maximum allowed iterations "
+ maxIterations);
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.alibaba.graphscope.common.ir.rel.type.TableConfig;
import com.alibaba.graphscope.common.ir.tools.AliasInference;
import com.alibaba.graphscope.common.ir.tools.GraphBuilder;
import com.alibaba.graphscope.common.ir.tools.LogicalPlan;
import com.alibaba.graphscope.common.ir.tools.QueryExecutionValidator;
import com.alibaba.graphscope.common.ir.tools.config.GraphOpt;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -58,6 +60,7 @@ public GraphTypeInference(GraphBuilder builder) {
* @return
*/
public RelNode inferTypes(RelNode top) {
if (new LogicalPlan(top).isReturnEmpty()) return top;
return visitRels(ImmutableList.of(top)).get(0);
}

Expand Down Expand Up @@ -847,6 +850,9 @@ public GraphPathTypeInference(
}

public GraphPathType inferPathType() {
if (this.maxHop > QueryExecutionValidator.SYSTEM_MAX_ITERATIONS) {
return this.pxdType;
}
recursive(startVType, new CompositePathType(Lists.newArrayList()), 0);
List<GraphLabelType.Entry> expandTypes = Lists.newArrayList();
List<GraphLabelType.Entry> getVTypes = Lists.newArrayList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1633,7 +1633,7 @@ public Traversal<Vertex, Long> get_g_VX2X_both_with_trail_allve_count() {
@Override
public Traversal<Vertex, Object> get_g_V_path_expand_until_age_gt_30_values_age() {
return ((IrCustomizedTraversal)
g.V().out("1..100", "knows")
g.V().out("1..15", "knows") // system maximum hops is 15
.with(
"UNTIL",
com.alibaba.graphscope.gremlin.integration.suite.utils
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package com.alibaba.graphscope.sdk;

import org.apache.commons.io.FileUtils;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

Expand Down Expand Up @@ -50,4 +51,25 @@ public void path_expand_test() throws Exception {
+ " src.__entity_id__ AS sId, dest.__entity_id__ AS dId;";
PlanUtils.compilePlan(configPath, query, schemaYaml, statsJson);
}

@Test
public void path_expand_max_hop_test() throws Exception {
try {
String query = "MATCH (src)-[e:test6*1..1000000]->(dest) Return src, dest";
PlanUtils.compilePlan(configPath, query, schemaYaml, statsJson);
} catch (Exception e) {
Assert.assertTrue(e.getMessage().contains("exceeds the maximum allowed iterations"));
}
}

@Test
public void path_expand_invalid_hop_test() throws Exception {
try {
// the max hop will be set as unlimited if it is less than min hop
String query = "MATCH (src)-[e:test6*5..4]->(dest) Return src, dest";
PlanUtils.compilePlan(configPath, query, schemaYaml, statsJson);
} catch (Exception e) {
Assert.assertTrue(e.getMessage().contains("exceeds the maximum allowed iterations"));
}
}
}

0 comments on commit 9c11778

Please sign in to comment.