From fbae168707a44f0e05fbc9d2623683acaaa7776b Mon Sep 17 00:00:00 2001 From: M <87920097+msgui@users.noreply.github.com> Date: Wed, 29 Nov 2023 21:44:29 +0800 Subject: [PATCH] feat: optimize perf for adjacency-edges query (#2242) --- hugegraph-server/hugegraph-api/pom.xml | 2 +- .../api/traversers/EdgeExistenceAPI.java | 84 +++++++++++++++++++ .../apache/hugegraph/version/ApiVersion.java | 13 +-- .../algorithm/EdgeExistenceTraverser.java | 66 +++++++++++++++ 4 files changed, 158 insertions(+), 7 deletions(-) create mode 100644 hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgeExistenceAPI.java create mode 100644 hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/EdgeExistenceTraverser.java diff --git a/hugegraph-server/hugegraph-api/pom.xml b/hugegraph-server/hugegraph-api/pom.xml index d365ce22f8..459a22e0ea 100644 --- a/hugegraph-server/hugegraph-api/pom.xml +++ b/hugegraph-server/hugegraph-api/pom.xml @@ -188,7 +188,7 @@ - 0.69.0.0 + 0.71.0.0 diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgeExistenceAPI.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgeExistenceAPI.java new file mode 100644 index 0000000000..6ffec166e1 --- /dev/null +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/api/traversers/EdgeExistenceAPI.java @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You 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 org.apache.hugegraph.api.traversers; + +import static org.apache.hugegraph.traversal.algorithm.HugeTraverser.DEFAULT_LIMIT; + +import java.util.Iterator; + +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.core.GraphManager; +import org.apache.hugegraph.structure.HugeVertex; +import org.apache.hugegraph.traversal.algorithm.EdgeExistenceTraverser; +import org.apache.hugegraph.util.E; +import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.structure.Edge; +import org.slf4j.Logger; + +import com.codahale.metrics.annotation.Timed; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.inject.Singleton; +import jakarta.ws.rs.DefaultValue; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.PathParam; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.QueryParam; +import jakarta.ws.rs.core.Context; + +@Path("graphs/{graph}/traversers/edgeexist") +@Singleton +@Tag(name = "EdgeExistenceAPI") +public class EdgeExistenceAPI extends TraverserAPI { + + private static final Logger LOG = Log.logger(EdgeExistenceAPI.class); + private static final String DEFAULT_EMPTY = ""; + + @GET + @Timed + @Produces(APPLICATION_JSON_WITH_CHARSET) + @Operation(summary = "get edges from 'source' to 'target' vertex") + public String get(@Context GraphManager manager, + @PathParam("graph") String graph, + @QueryParam("source") String source, + @QueryParam("target") String target, + @QueryParam("label") String edgeLabel, + @QueryParam("sort_values") + @DefaultValue(DEFAULT_EMPTY) String sortValues, + @QueryParam("limit") + @DefaultValue(DEFAULT_LIMIT) long limit) { + LOG.debug("Graph [{}] get edgeExistence with " + + "source '{}', target '{}', edgeLabel '{}', sortValue '{}', limit '{}'", + graph, source, target, edgeLabel, sortValues, limit); + + E.checkArgumentNotNull(source, "The source can't be null"); + E.checkArgumentNotNull(target, "The target can't be null"); + + Id sourceId = HugeVertex.getIdValue(source); + Id targetId = HugeVertex.getIdValue(target); + HugeGraph hugegraph = graph(manager, graph); + EdgeExistenceTraverser traverser = new EdgeExistenceTraverser(hugegraph); + Iterator edges = traverser.queryEdgeExistence(sourceId, targetId, edgeLabel, + sortValues, limit); + + return manager.serializer(hugegraph).writeEdges(edges, false); + } +} diff --git a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java index 8170827631..a6b7f7c241 100644 --- a/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java +++ b/hugegraph-server/hugegraph-api/src/main/java/org/apache/hugegraph/version/ApiVersion.java @@ -29,7 +29,7 @@ public final class ApiVersion { * [0.2] HugeGraph-527: First add the version to the hugegraph module * [0.3] HugeGraph-525: Add versions check of components and api * [0.4] HugeGraph-162: Add schema builder to separate client and inner interface. - * [0.5] HugeGraph-498: Support three kind of id strategy + * [0.5] HugeGraph-498: Support three kinds of id strategy *

* version 0.3: *

@@ -41,12 +41,12 @@ public final class ApiVersion { *

* version 0.4: * [0.11] HugeGraph-938: Remove useless index-names field in VL/EL API - * [0.12] HugeGraph-589: Add schema id for all schema element + * [0.12] HugeGraph-589: Add schema id for all schema elements * [0.13] HugeGraph-956: Support customize string/number id strategy *

* version 0.5: * [0.14] HugeGraph-1085: Add enable_label_index to VL/EL - * [0.15] HugeGraph-1105: Support paging for large amounts of records + * [0.15] HugeGraph-1105: Support paging for large numbers of records * [0.16] HugeGraph-944: Support rest shortest path, k-out, k-neighbor * [0.17] HugeGraph-944: Support rest shortest path, k-out, k-neighbor * [0.18] HugeGraph-81: Change argument "checkVertex" to "check_vertex" @@ -75,7 +75,7 @@ public final class ApiVersion { * [0.34] Issue-307: Let VertexAPI use simplified property serializer * [0.35] Issue-287: Support pagination when do index query * [0.36] Issue-360: Support paging for scan api - * [0.37] Issue-391: Add skip_super_node for shortest path + * [0.37] Issue-391: Add skip_super_node for the shortest path * [0.38] Issue-274: Add personal-rank and neighbor-rank RESTful API *

* version 0.10: @@ -114,17 +114,18 @@ public final class ApiVersion { * [0.67] Issue-1065: Support dynamically add/remove graph * [0.68] Issue-1763: Support adamic-adar & resource-allocation API * [0.69] Issue-1748: Support Cypher query RESTful API - * [0.70] Issue-2242: Optimising adjacency edge queries + * [0.70] PR-2242: Add edge-existence RESTful API * [0.71] PR-2286: Support Arthas API & Metric API prometheus format */ /** * The second parameter of Version.of() is for IDE running without JAR + * Note: Also update the version number in hugegraph-api/pom.xml */ public static final Version VERSION = Version.of(ApiVersion.class, "0.71"); public static void check() { - // Check version of hugegraph-core. Firstly do check from version 0.3 + // Check the version of hugegraph-core. Do first check from version 0.3 VersionUtil.check(CoreVersion.VERSION, "1.0", "1.6", CoreVersion.NAME); } } diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/EdgeExistenceTraverser.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/EdgeExistenceTraverser.java new file mode 100644 index 0000000000..a7005ad867 --- /dev/null +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/traversal/algorithm/EdgeExistenceTraverser.java @@ -0,0 +1,66 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with this + * work for additional information regarding copyright ownership. The ASF + * licenses this file to You 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 org.apache.hugegraph.traversal.algorithm; + +import java.util.Iterator; + +import org.apache.hugegraph.HugeGraph; +import org.apache.hugegraph.backend.id.Id; +import org.apache.hugegraph.backend.query.ConditionQuery; +import org.apache.hugegraph.iterator.FilterIterator; +import org.apache.hugegraph.schema.EdgeLabel; +import org.apache.hugegraph.type.HugeType; +import org.apache.hugegraph.type.define.Directions; +import org.apache.hugegraph.type.define.HugeKeys; +import org.apache.tinkerpop.gremlin.structure.Edge; + +public class EdgeExistenceTraverser extends HugeTraverser { + + public EdgeExistenceTraverser(HugeGraph graph) { + super(graph); + } + + public Iterator queryEdgeExistence(Id sourceId, Id targetId, String label, + String sortValues, long limit) { + // If no label provided, fallback to a slow query by filtering + if (label == null || label.isEmpty()) { + return queryByNeighbors(sourceId, targetId, limit); + } + + Id edgeLabelId = getEdgeLabelId(label); + EdgeLabel edgeLabel = graph().edgeLabel(edgeLabelId); + ConditionQuery conditionQuery = new ConditionQuery(HugeType.EDGE); + conditionQuery.eq(HugeKeys.OWNER_VERTEX, sourceId); + conditionQuery.eq(HugeKeys.OTHER_VERTEX, targetId); + conditionQuery.eq(HugeKeys.LABEL, edgeLabelId); + conditionQuery.eq(HugeKeys.DIRECTION, Directions.OUT); + conditionQuery.limit(limit); + + if (edgeLabel.existSortKeys()) { + conditionQuery.eq(HugeKeys.SORT_VALUES, sortValues); + } else { + conditionQuery.eq(HugeKeys.SORT_VALUES, ""); + } + return graph().edges(conditionQuery); + } + + private Iterator queryByNeighbors(Id sourceId, Id targetId, long limit) { + return new FilterIterator<>(edgesOfVertex(sourceId, Directions.OUT, (Id) null, limit), + edge -> targetId.equals(edge.inVertex().id())); + } +}