通过JNI和RESTful API实现的Graph Planner接口

GraphPlanner 是 GOpt 查询优化与物理计划生成的主要入口点。最初,它与前端服务紧密集成,负责优化通过 Bolt 协议接收的 Cypher 查询,并为不同后端引擎生成执行计划。

为了增强其灵活性和集成便利性,GraphPlanner现已作为独立模块提供,无需依赖其他前端模块。它同时支持JNI和RESTful API接口,能够轻量级且直接地集成到各类系统中。无论您是在开发原生应用还是基于Web的服务,GraphPlanner都能无缝融入您的架构,为广泛的使用场景提供高效的查询优化和物理计划生成能力。

JNI API

接口概览

我们为JNI接口提供了一个C++封装实现GraphPlannerWrapper。以下是该c++类提供的逻辑接口的简要说明。

构造函数:

/**
 * @brief Constructs a new GraphPlannerWrapper object
 * @param java_path Java class path
 * @param jna_path JNA library path
 * @param graph_schema_yaml Path to the graph schema file in YAML format (optional)
 * @param graph_statistic_json Path to the graph statistics file in JSON format (optional)
 */
GraphPlannerWrapper(const std::string &java_path,
                    const std::string &jna_path,
                    const std::string &graph_schema_yaml = "",
                    const std::string &graph_statistic_json = "");

方法:

/**
 * @brief Compile a cypher query to a physical plan by JNI invocation.
 * @param compiler_config_path The path of compiler config file.
 * @param cypher_query_string The cypher query string.
 * @param graph_schema_yaml Content of the graph schema in YAML format
 * @param graph_statistic_json Content of the graph statistics in JSON format
 * @return The physical plan in bytes and result schema in yaml.
 */
Plan GraphPlannerWrapper::CompilePlan(const std::string &compiler_config_path,
                                      const std::string &cypher_query_string,
                                      const std::string &graph_schema_yaml,
                                      const std::string &graph_statistic_json)

快速入门

按照以下步骤开始使用Graph Planner接口进行C++调用。

步骤1:构建项目

导航到项目目录并使用Maven构建包:

cd interactive_engine
mvn clean package -DskipTests -Pgraph-planner-jni

步骤2:定位并提取软件包

构建完成后,一个名为graph-planner-jni.tar.gz的压缩包将出现在assembly/target目录中。解压该压缩包的内容:

cd assembly/target
tar xvzf graph-planner-jni.tar.gz
cd graph-planner-jni

步骤3:运行示例二进制文件

为了演示JNI接口的用法,提供了一个示例二进制文件test_graph_planner。使用以下命令执行它:

# bin/test_graph_planner <java class path> <jna lib path> <graph schema path> <graph statistics path> <query> <config path>
bin/test_graph_planner libs native ./conf/graph.yaml ./conf/modern_statistics.json "MATCH (n) RETURN n, COUNT(n);" ./conf/gs_interactive_hiactor.yaml

输出包含物理计划(以字节格式)和结果模式(以YAML格式)。该物理计划遵循protobuf中定义的规范。

以下是一个结果模式的示例:

schema:
  name: default
  description: default desc
  mode: READ
  extension: .so
  library: libdefault.so
  params: []
returns:
  - name: n
    type: {primitive_type: DT_UNKNOWN}
  - name: $f1
    type: {primitive_type: DT_SIGNED_INT64}
type: UNKNOWN
query: MATCH (n) RETURN n, COUNT(n);

returns字段定义了后端引擎返回数据的结构。returns字段中的每个嵌套条目包含三个组成部分:

  • 列名,用于指定结果列的名称;

  • 条目的序号位置,决定了列ID;

  • 类型,用于对列强制执行数据类型约束。

Restful API

我们提供另一种方法将接口作为RESTful API公开。按照以下步骤通过REST访问接口。

快速入门

步骤1:构建项目

要构建项目,请运行以下命令:

cd interactive_engine
# Use '-Dskip.native=true' to skip compiling C++ native code
mvn clean package -DskipTests -Pgraph-planner-jni -Dskip.native=true

步骤2:定位并提取软件包

构建完成后,一个名为graph-planner-jni.tar.gz的压缩包将出现在assembly/target目录中。按以下方式解压内容:

cd assembly/target
tar xvzf graph-planner-jni.tar.gz
cd graph-planner-jni

步骤3:启动Graph Planner RESTful服务

要启动服务,请运行以下命令:

java -cp ".:./libs/*" com.alibaba.graphscope.sdk.restful.GraphPlannerService --spring.config.location=./conf/application.yaml

步骤4:通过Curl访问RESTful API

要向RESTful API发送请求,请使用以下curl命令:

curl -X POST http://localhost:8080/api/compilePlan \
    -H "Content-Type: application/json" \
    -d "{
        \"configPath\": \"$configPath\",
        \"query\": \"$query\",
        \"schemaYaml\": \"$schemaYaml\",
        \"statsJson\": \"$statsJson\"
    }"

$configPath$query$schemaYaml$statsJson 替换为适当的值。

响应将以JSON格式返回,类似如下:

{
    "graphPlan": {
        "physicalBytes": "",
        "resultSchemaYaml": ""
    }
}

响应包含两个字段:

  1. physicalBytes: 一个Base64编码的字符串,表示物理计划的字节数据。

  2. resultSchemaYaml: 表示YAML模式的字符串。

您可以将这些值解码为所需的结构。

步骤4:通过Java SDK访问RESTful API

另外,如果您是Java端用户,我们提供了一个Java SDK示例,指导您如何访问RESTful API并解码响应:

public static void main(String[] args) throws Exception {
    if (args.length < 4) {
        System.out.println("Usage: <configPath> <query> <schemaPath> <statsPath>");
        System.exit(1);
    }
    // set request body in json format
    String jsonPayLoad = createParameters(args[0], args[1], args[2], args[3]).toString();
    HttpClient client = HttpClient.newBuilder().build();
    // create http request, set header and body content
    HttpRequest request =
            HttpRequest.newBuilder()
                    .uri(URI.create("http://localhost:8080/api/compilePlan"))
                    .setHeader("Content-Type", "application/json")
                    .POST(
                            HttpRequest.BodyPublishers.ofString(
                                    jsonPayLoad, StandardCharsets.UTF_8))
                    .build();
    // send request and get response
    HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
    String body = response.body();
    // parse response body as json
    JsonNode planNode = (new ObjectMapper()).readTree(body).get("graphPlan");
    // print result
    System.out.println(getPhysicalPlan(planNode));
    System.out.println(getResultSchemaYaml(planNode));
}

private static JsonNode createParameters(
        String configPath, String query, String schemaPath, String statsPath) throws Exception {
    Map<String, String> params =
            ImmutableMap.of(
                    "configPath",
                    configPath,
                    "query",
                    query,
                    "schemaYaml",
                    FileUtils.readFileToString(new File(schemaPath), StandardCharsets.UTF_8),
                    "statsJson",
                    FileUtils.readFileToString(new File(statsPath), StandardCharsets.UTF_8));
    return (new ObjectMapper()).valueToTree(params);
}

// get base64 string from json, convert it to physical bytes , then parse it to PhysicalPlan
private static GraphAlgebraPhysical.PhysicalPlan getPhysicalPlan(JsonNode planNode)
        throws Exception {
    String base64Str = planNode.get("physicalBytes").asText();
    byte[] bytes = java.util.Base64.getDecoder().decode(base64Str);
    return GraphAlgebraPhysical.PhysicalPlan.parseFrom(bytes);
}

// get result schema yaml from json
private static String getResultSchemaYaml(JsonNode planNode) {
    return planNode.get("resultSchemaYaml").asText();
}

使用以下命令运行Java SDK示例:

java -cp ".:./libs/*" com.alibaba.graphscope.sdk.examples.TestGraphPlanner ./conf/gs_interactive_hiactor.yaml "Match (n) Return n;" ./conf/graph.yaml ./conf/modern_statistics.json