GART:弥合关系型OLTP与图工作负载之间的鸿沟

GART是一个松耦合框架,可部署用于桥接现有关系型OLTP系统(如MySQL)与图专用系统GraphScope。 简而言之,GART利用现代OLTP系统的高可用性机制,并借助OLTP系统的预写日志(WALs)来提取图数据,从而在GraphScope的执行引擎上执行图相关计算工作负载。 与离线数据迁移不同,GART通过复用WALs在线重放图数据,并支持多版本功能。

架构与工作流程

下图展示了GART架构的概览,它复用了来自OLTP专用系统(如MySQL)和GAP专用系统(GraphScope)的现有执行引擎。 事务在OLTP引擎中提交和处理,并更新关系型存储中的数据。 在事务提交过程中,系统还会生成日志以记录更新操作,确保高可用性。 GART利用这些日志将关系型数据转换为图数据,并将更新存储在动态图存储中。此后,GraphScope可以基于动态图存储处理图计算工作负载。

Architecture of GART.

GART的架构。

在GART中定义图映射

用户首先需要定义将关系型数据转换为属性图的规则。 具体来说,在GART中,每个顶点/边类型对应OLTP系统中的一个表,而顶点/边的每个属性则对应表中的某个属性(列)。 要在GART中定义图映射,用户需要提供一个JSON配置文件,其格式应如下所示。

将日志加载到GART存储

考虑到不同OLTP系统中日志格式的多样性,GART提出了一种统一的日志格式。 来自OLTP系统的日志需要先转换为统一日志格式才能使用。 如果您使用MySQL作为OLTP系统,可以使用Maxwell将binlog转换为JSON格式。 然后,GART提供了一个工具来进一步将JSON格式转换为统一格式。 当转换工具接收到日志时,它会从关系数据源中提取数据和元数据更新,以生成兼容不同日志格式的统一日志。

默认情况下,GART从Kafka读取统一日志,并根据关系型数据转换为属性图的规则,将这些日志应用于图相关操作,如在动态图存储中添加、删除或更新顶点/边。 该图存储允许GART和GraphScope的执行引擎同时读写图数据。 动态图存储提供了图数据的一致且最新的视图,这意味着图数据是从关系型数据派生出的稳定快照。

使用GraphScope加载的图

加载到GART后,图的模式信息会被写入etcd。随后,GraphScope的执行引擎可以读取图的模式信息来构建GART片段。为此,您需要为GART和GraphScope都分配一个etcd端点。要构建GART片段,您需要提供一个存储模式信息的etcd端点,以及您想要读取的版本号。

void* BuildGartFragment(std::string etcd_endpoint, int version) {
  using GraphType = gart::GartFragment<uint64_t, uint64_t>;
  auto fragment = 
      new gart::GartFragment<uint64_t, uint64_t>());
  std::shared_ptr<etcd::Client> etcd_client =
        std::make_shared<etcd::Client>(etcd_endpoint);
  grape::CommSpec comm_spec;
  comm_spec.Init(MPI_COMM_WORLD);
  std::string schema_key = "gart_schema_p" + std::to_string(comm_spec.fid());
  etcd::Response response = etcd_client->get(schema_key).get();
  std::string edge_config_str = response.value().as_string();
  schema_key = "gart_blob_m" + std::to_string(0) + "_p" +
               std::to_string(comm_spec.fid()) + "_e" +
               std::to_string(version);
  response = etcd_client->get(schema_key).get();
  std::string config_str = response.value().as_string();
  json config = json::parse(config_str);
  json edge_config = json::parse(edge_config_str);

  fragment->Init(config, edge_config);
  return fragment;
}

在构建GART片段后,您可以访问图拓扑结构以及顶点/边属性。

void Traverse(void* frag, size_t frag_id) {
  using GraphType = gart::GartFragment<uint64_t, uint64_t>;
  auto fragment = static_cast<GraphType>(frag);
  auto vertex_label_num = fragment->vertex_label_num();
  auto edge_label_num = fragment->edge_label_num();

  for (auto v_label = 0; v_label < vertex_label_num; v_label++) {
    auto inner_vertices_iter = fragment->InnerVertices(v_label);
    while (inner_vertices_iter.valid()) {
      auto src = inner_vertices_iter.vertex();
      for (auto elabel = 0; elabel < edge_label_num; elabel++) {
        auto edge_iter = fragment->GetOutgoingAdjList(src, elabel);
        while (edge_iter.valid()) {
          auto dst = edge_iter.neighbor();
          edge_iter.next();
        }
      }
      inner_vertices_iter.next();
    }
  }
}