GART:弥合关系型OLTP与图工作负载之间的鸿沟¶
GART是一个松耦合框架,可部署用于桥接现有关系型OLTP系统(如MySQL)与图专用系统GraphScope。 简而言之,GART利用现代OLTP系统的高可用性机制,并借助OLTP系统的预写日志(WALs)来提取图数据,从而在GraphScope的执行引擎上执行图相关计算工作负载。 与离线数据迁移不同,GART通过复用WALs在线重放图数据,并支持多版本功能。
架构与工作流程¶
下图展示了GART架构的概览,它复用了来自OLTP专用系统(如MySQL)和GAP专用系统(GraphScope)的现有执行引擎。 事务在OLTP引擎中提交和处理,并更新关系型存储中的数据。 在事务提交过程中,系统还会生成日志以记录更新操作,确保高可用性。 GART利用这些日志将关系型数据转换为图数据,并将更新存储在动态图存储中。此后,GraphScope可以基于动态图存储处理图计算工作负载。
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();
}
}
}